vibepup 1.0.0 → 1.0.2

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  > **"Fetch Code. Sit. Stay. Good Pup."**
4
4
 
5
+ ![Corgi Pup Illustration](https://raw.githubusercontent.com/shantanusoam/Vibepup/refs/heads/gh-images/assets/corgi_pup_ilustration.png)
6
+
5
7
  Vibepup is a **Split-Brain Autonomous Agent Harness** that lives in your terminal. A loyal helper built for **DX-first, vibe-coding energy**.
6
8
 
7
9
  **Mascot:** Pummy the cyberpunk corgi.
@@ -20,6 +22,7 @@ Most AI agents are black boxes that overwrite your files and get stuck in loops.
20
22
  - Safe, loop-resistant agent harness
21
23
  - Minimal setup, works everywhere
22
24
  - Loyal helper with a cyberpunk-cute mascot
25
+ - Helpful onboarding for free-tier access
23
26
 
24
27
  * **🧠 Split-Brain**: Keeps your instructions (`prd.md`) separate from internal state (`prd.state.json`). Edit tasks mid-run without breaking the agent.
25
28
  * **🛡️ Anti-Wizard**: Refuses to run interactive commands that hang shells. Vibepup forces clarity.
@@ -49,6 +52,16 @@ bunx vibepup --watch
49
52
  bun add -g vibepup
50
53
  ```
51
54
 
55
+ ### 1d. TUI mode (optional)
56
+ ```bash
57
+ vibepup --tui
58
+ ```
59
+
60
+ ### 1e. Free setup (one command)
61
+ ```bash
62
+ vibepup free
63
+ ```
64
+
52
65
  ### 2. Fetch!
53
66
  Go to any empty folder and tell Vibepup what to build.
54
67
 
@@ -79,8 +92,41 @@ vibepup --watch
79
92
  ```
80
93
  In watch mode, Vibepup keeps working until the PRD is done. If you edit `prd.md` (e.g., add "- [ ] Add dark mode"), he smells the change and gets back to work immediately.
81
94
 
95
+ ### 4. TUI Mode
96
+ ```bash
97
+ vibepup --tui
98
+ ```
99
+ TUI mode provides a Bubble Tea interface with puppy animation, quick mode selection, and a clean launch experience.
100
+
82
101
  ## ⚙️ Configuration
83
- Vibepup works out of the box, but you can tune his brain in `~/.config/ralph/config.json`:
102
+ Vibepup works out of the box. For the easiest free-tier bootstrap, run:
103
+
104
+ ```bash
105
+ vibepup free
106
+ ```
107
+
108
+ If `opencode` is missing, Vibepup will try to install it on Linux/macOS and then guide you. You can also set up a free tier manually:
109
+
110
+ ```bash
111
+ npm install -g opencode-antigravity-auth
112
+ opencode auth login antigravity
113
+ ```
114
+
115
+ If you cannot open a browser on the target machine:
116
+ ```bash
117
+ opencode auth print-token antigravity
118
+ export OPENCODE_ANTIGRAVITY_TOKEN="<token>"
119
+ ```
120
+
121
+ ### TUI build (optional)
122
+ TUI requires Go 1.22+.
123
+ ```bash
124
+ cd npm-package/tui
125
+
126
+ go build -o vibepup-tui
127
+ ```
128
+
129
+ Vibepup also supports manual config in `~/.config/ralph/config.json`:
84
130
 
85
131
  ```json
86
132
  {
package/bin/ralph.js CHANGED
@@ -6,7 +6,9 @@ const os = require('os');
6
6
  const fs = require('fs');
7
7
 
8
8
  const scriptPath = path.join(__dirname, '../lib/ralph.sh');
9
- const args = process.argv.slice(2);
9
+ const allArgs = process.argv.slice(2);
10
+ const useTui = allArgs.includes('--tui');
11
+ const args = allArgs.filter((arg) => arg !== '--tui');
10
12
 
11
13
  try {
12
14
  fs.chmodSync(scriptPath, '755');
@@ -14,17 +16,45 @@ try {
14
16
 
15
17
  console.log('🐾 Vibepup is waking up...');
16
18
 
17
- let command = scriptPath;
18
- let cmdArgs = args;
19
- let shellOptions = { stdio: 'inherit', shell: false };
19
+ const shellOptions = { stdio: 'inherit', shell: false };
20
20
 
21
+ if (useTui) {
22
+ const tuiDir = path.join(__dirname, '../tui');
23
+ const binName = os.platform() === 'win32' ? 'vibepup-tui.exe' : 'vibepup-tui';
24
+ const binPath = path.join(tuiDir, binName);
25
+
26
+ if (fs.existsSync(binPath)) {
27
+ const tui = spawn(binPath, args, shellOptions);
28
+ tui.on('error', (err) => {
29
+ console.error('❌ Failed to start Vibepup TUI.');
30
+ console.error(String(err));
31
+ process.exit(1);
32
+ });
33
+ tui.on('close', (code) => process.exit(code));
34
+ return;
35
+ }
36
+
37
+ if (os.platform() !== 'win32' && fs.existsSync(tuiDir)) {
38
+ const goCmd = spawn('go', ['run', '.', ...args], { ...shellOptions, cwd: tuiDir });
39
+ goCmd.on('error', (err) => {
40
+ console.error('❌ Failed to start Vibepup TUI.');
41
+ console.error(String(err));
42
+ process.exit(1);
43
+ });
44
+ goCmd.on('close', (code) => process.exit(code));
45
+ return;
46
+ }
47
+
48
+ console.error('❌ Vibepup TUI not available.');
49
+ console.error(' Build it with:');
50
+ console.error(' cd ' + tuiDir + ' && go build -o ' + binName);
51
+ process.exit(1);
52
+ }
53
+
54
+ let command = 'bash';
55
+ let cmdArgs = [scriptPath, ...args];
21
56
  if (os.platform() === 'win32') {
22
- command = 'bash';
23
- cmdArgs = [scriptPath, ...args];
24
- shellOptions.shell = true;
25
- } else {
26
- command = 'bash';
27
- cmdArgs = [scriptPath, ...args];
57
+ shellOptions.shell = true;
28
58
  }
29
59
 
30
60
  const vibepup = spawn(command, cmdArgs, shellOptions);
package/lib/ralph.sh CHANGED
@@ -68,9 +68,14 @@ trap "pkill -P $$; exit" SIGINT SIGTERM
68
68
  # --- Parse Args ---
69
69
  MODE="default"
70
70
  PROJECT_IDEA=""
71
+ FREE_MODE="false"
71
72
 
72
73
  while [[ "$#" -gt 0 ]]; do
73
74
  case $1 in
75
+ free)
76
+ FREE_MODE="true"
77
+ shift
78
+ ;;
74
79
  new)
75
80
  MODE="new"
76
81
  PROJECT_IDEA="$2"
@@ -85,6 +90,57 @@ echo "🐾 Vibepup v1.0 (CLI Mode)"
85
90
  echo " Engine: $ENGINE_DIR"
86
91
  echo " Context: $PROJECT_DIR"
87
92
 
93
+ echo " Tips:"
94
+ echo " - Run 'vibepup free' for free-tier setup"
95
+ echo " - Run 'vibepup new ""My idea""' to bootstrap a project"
96
+ echo " - Run 'vibepup --tui' for a guided interface"
97
+
98
+ type -p opencode >/dev/null 2>&1 || {
99
+ if command -v curl >/dev/null 2>&1; then
100
+ UNAME=$(uname -s)
101
+ if [ "$UNAME" = "Linux" ] || [ "$UNAME" = "Darwin" ]; then
102
+ echo "⚠️ opencode not found. Installing..."
103
+ curl -fsSL https://opencode.ai/install | bash || true
104
+ fi
105
+ fi
106
+ }
107
+
108
+ if ! command -v opencode >/dev/null 2>&1; then
109
+ if [[ "$FREE_MODE" == "true" ]]; then
110
+ echo "🔧 Free setup: installing opencode..."
111
+ if command -v npm >/dev/null 2>&1; then
112
+ npm install -g opencode-ai opencode-antigravity-auth || true
113
+ else
114
+ echo "❌ npm not found. Install Node.js or use:"
115
+ echo " curl -fsSL https://opencode.ai/install | bash"
116
+ exit 127
117
+ fi
118
+ else
119
+ echo "❌ opencode not found. Vibepup requires opencode to run."
120
+ echo " Install with one of:"
121
+ echo " - curl -fsSL https://opencode.ai/install | bash"
122
+ echo " - npm install -g opencode-ai"
123
+ echo " - brew install anomalyco/tap/opencode"
124
+ echo " Free-tier option:"
125
+ echo " - vibepup free"
126
+ exit 127
127
+ fi
128
+ fi
129
+
130
+ if [[ "$FREE_MODE" == "true" ]]; then
131
+ echo "✨ Vibepup Free Setup"
132
+ echo " 1) Installing auth plugin"
133
+ if command -v npm >/dev/null 2>&1; then
134
+ npm install -g opencode-antigravity-auth || true
135
+ fi
136
+ echo " 2) Starting Google auth"
137
+ opencode auth login antigravity || true
138
+ echo " 3) Refreshing models"
139
+ opencode models --refresh || true
140
+ echo "✅ Free setup complete. Run 'vibepup --watch' next."
141
+ exit 0
142
+ fi
143
+
88
144
  # --- Smart Model Discovery ---
89
145
  get_available_models() {
90
146
  local PREF_MODELS=("$@")
@@ -92,7 +148,8 @@ get_available_models() {
92
148
 
93
149
  echo "🔍 Verifying available models..." >&2
94
150
 
95
- local ALL_MODELS=$(opencode models --refresh | grep -E "^[a-z0-9-]+\/[a-z0-9.-]+" || true)
151
+ local ALL_MODELS
152
+ ALL_MODELS=$(opencode models --refresh 2>/dev/null | grep -E "^[a-z0-9-]+/[a-z0-9.-]+" || true)
96
153
 
97
154
  for PREF in "${PREF_MODELS[@]}"; do
98
155
  if echo "$ALL_MODELS" | grep -q "^$PREF$"; then
@@ -105,6 +162,11 @@ get_available_models() {
105
162
  AVAILABLE_MODELS+=($(echo "$ALL_MODELS" | grep "gpt-4o" | head -n 1))
106
163
  AVAILABLE_MODELS+=($(echo "$ALL_MODELS" | grep "claude-sonnet" | head -n 1))
107
164
  fi
165
+
166
+ if [ ${#AVAILABLE_MODELS[@]} -eq 0 ]; then
167
+ AVAILABLE_MODELS=("opencode/grok-code")
168
+ echo "⚠️ Using fallback model: opencode/grok-code" >&2
169
+ fi
108
170
 
109
171
  echo "${AVAILABLE_MODELS[@]}"
110
172
  }
@@ -265,7 +327,6 @@ run_agent() {
265
327
  --file "$PROJECT_DIR/repo-map.md" \
266
328
  --file "$ITER_DIR/progress.tail.log" \
267
329
  "${EXTRA_ARGS[@]}" \
268
- --agent general \
269
330
  --model "$MODEL"
270
331
  }
271
332
 
@@ -316,6 +377,11 @@ while true; do
316
377
  set -e
317
378
 
318
379
  RESPONSE=$(cat "$ITER_DIR/agent_response.txt")
380
+
381
+ if echo "$RESPONSE" | grep -qi "not supported\|ModelNotFoundError\|Make sure the model is enabled"; then
382
+ echo " ⚠️ Model $MODEL not supported. Falling back..."
383
+ continue
384
+ fi
319
385
 
320
386
  if [ $EXIT_CODE -eq 0 ] && [ -n "$RESPONSE" ]; then
321
387
  SUCCESS=true
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "vibepup",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A loyal, DX-first split-brain agent harness with cyberpunk vibes.",
5
- "bin": "bin/ralph.js",
5
+ "bin": {
6
+ "vibepup": "bin/ralph.js"
7
+ },
6
8
  "scripts": {
7
9
  "test": "echo \"Error: no test specified\" && exit 1"
8
10
  },
package/tui/go.mod ADDED
@@ -0,0 +1,37 @@
1
+ module vibepup-tui
2
+
3
+ go 1.22
4
+
5
+ require (
6
+ github.com/charmbracelet/bubbletea v1.1.0
7
+ github.com/charmbracelet/harmonica v0.2.0
8
+ github.com/charmbracelet/huh v0.6.0
9
+ github.com/charmbracelet/lipgloss v0.13.0
10
+ github.com/charmbracelet/log v0.3.1
11
+ )
12
+
13
+ require (
14
+ github.com/atotto/clipboard v0.1.4 // indirect
15
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
16
+ github.com/catppuccin/go v0.2.0 // indirect
17
+ github.com/charmbracelet/bubbles v0.20.0 // indirect
18
+ github.com/charmbracelet/x/ansi v0.2.3 // indirect
19
+ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
20
+ github.com/charmbracelet/x/term v0.2.0 // indirect
21
+ github.com/dustin/go-humanize v1.0.1 // indirect
22
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
23
+ github.com/go-logfmt/logfmt v0.6.0 // indirect
24
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
25
+ github.com/mattn/go-isatty v0.0.20 // indirect
26
+ github.com/mattn/go-localereader v0.0.1 // indirect
27
+ github.com/mattn/go-runewidth v0.0.16 // indirect
28
+ github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
29
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
30
+ github.com/muesli/cancelreader v0.2.2 // indirect
31
+ github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
32
+ github.com/rivo/uniseg v0.4.7 // indirect
33
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
34
+ golang.org/x/sync v0.8.0 // indirect
35
+ golang.org/x/sys v0.25.0 // indirect
36
+ golang.org/x/text v0.18.0 // indirect
37
+ )
package/tui/go.sum ADDED
@@ -0,0 +1,69 @@
1
+ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
2
+ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
3
+ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
4
+ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
5
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
6
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
7
+ github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
8
+ github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
9
+ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
10
+ github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
11
+ github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
12
+ github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
13
+ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
14
+ github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
15
+ github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
16
+ github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
17
+ github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
18
+ github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
19
+ github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
20
+ github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
21
+ github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
22
+ github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
23
+ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
24
+ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
25
+ github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
26
+ github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
27
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
28
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
29
+ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
30
+ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
31
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
32
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
33
+ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
34
+ github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
35
+ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
36
+ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
37
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
38
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
39
+ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
40
+ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
41
+ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
42
+ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
43
+ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
44
+ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
45
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
46
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
47
+ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
48
+ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
49
+ github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
50
+ github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
51
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
52
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
53
+ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
54
+ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
55
+ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
56
+ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
57
+ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
58
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
59
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
60
+ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
61
+ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
62
+ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
63
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
64
+ golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
65
+ golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
66
+ golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
67
+ golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
68
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
69
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package/tui/main.go ADDED
@@ -0,0 +1,281 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+ "os/exec"
8
+ "strings"
9
+ "time"
10
+
11
+ tea "github.com/charmbracelet/bubbletea"
12
+ "github.com/charmbracelet/harmonica"
13
+ "github.com/charmbracelet/huh"
14
+ "github.com/charmbracelet/lipgloss"
15
+ "github.com/charmbracelet/log"
16
+ )
17
+
18
+ type viewState int
19
+
20
+ const (
21
+ stateSplash viewState = iota
22
+ stateSetup
23
+ stateRunning
24
+ stateDone
25
+ )
26
+
27
+ type model struct {
28
+ state viewState
29
+ start time.Time
30
+ frame int
31
+ args []string
32
+ form *huh.Form
33
+ newForm *huh.Form
34
+ selected string
35
+ newIdea string
36
+ spring harmonica.Spring
37
+ pos float64
38
+ velocity float64
39
+ targetPos float64
40
+ }
41
+
42
+ func initialModel() model {
43
+ args := []string{"--watch"}
44
+ if len(os.Args) > 1 {
45
+ args = os.Args[1:]
46
+ }
47
+
48
+ spring := harmonica.NewSpring(harmonica.FPS(60), 6.0, 0.2)
49
+
50
+ m := model{state: stateSplash, start: time.Now(), args: args, selected: "watch", spring: spring, targetPos: 10}
51
+ m.form = huh.NewForm(
52
+ huh.NewGroup(
53
+ huh.NewSelect[string]().
54
+ Title("♥ What shall we do today? ♥").
55
+ Options(
56
+ huh.NewOption("Watch (recommended)", "watch"),
57
+ huh.NewOption("Run 5 iterations", "run"),
58
+ huh.NewOption("New project from idea", "new"),
59
+ huh.NewOption("Free setup (opencode + auth)", "free"),
60
+ ).
61
+ Value(&m.selected),
62
+ ),
63
+ )
64
+ m.newForm = huh.NewForm(
65
+ huh.NewGroup(
66
+ huh.NewInput().
67
+ Title("Describe your project idea").
68
+ Prompt("Idea: ").
69
+ Placeholder("A vibe-coded project").
70
+ Value(&m.newIdea),
71
+ ),
72
+ )
73
+
74
+ return m
75
+ }
76
+
77
+ func (m model) Init() tea.Cmd {
78
+ log.SetLevel(log.InfoLevel)
79
+ log.SetReportCaller(false)
80
+ log.SetTimeFormat("")
81
+ return tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
82
+ }
83
+
84
+ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
85
+ switch msg := msg.(type) {
86
+ case tea.KeyMsg:
87
+ if msg.String() == "ctrl+c" || msg.String() == "esc" {
88
+ return m, tea.Quit
89
+ }
90
+ case time.Time:
91
+ switch m.state {
92
+ case stateSplash:
93
+ m.frame++
94
+ if time.Since(m.start) > time.Second*2 {
95
+ m.state = stateSetup
96
+ return m, m.form.Init()
97
+ }
98
+ return m, tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
99
+ case stateRunning:
100
+ m.frame++
101
+ if m.frame%20 == 0 {
102
+ if m.targetPos == 0 {
103
+ m.targetPos = 10
104
+ } else {
105
+ m.targetPos = 0
106
+ }
107
+ }
108
+ m.pos, m.velocity = m.spring.Update(m.pos, m.velocity, m.targetPos)
109
+ return m, tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
110
+ }
111
+ }
112
+
113
+ switch m.state {
114
+ case stateSetup:
115
+ fm, cmd := m.form.Update(msg)
116
+ m.form = fm.(*huh.Form)
117
+ if m.form.State == huh.StateCompleted {
118
+ if m.selected == "new" {
119
+ m.state = stateRunning
120
+ return m, m.newForm.Init()
121
+ }
122
+ m.state = stateRunning
123
+ return m, launchCmd(m.selected, m.args)
124
+ }
125
+ return m, cmd
126
+ case stateRunning:
127
+ if m.selected == "new" {
128
+ fm, cmd := m.newForm.Update(msg)
129
+ m.newForm = fm.(*huh.Form)
130
+ if m.newForm.State == huh.StateCompleted {
131
+ idea := strings.TrimSpace(m.newIdea)
132
+ if idea == "" {
133
+ idea = "A vibe-coded project"
134
+ }
135
+ return m, launchCmd("new", append([]string{idea}, m.args...))
136
+ }
137
+ return m, cmd
138
+ }
139
+ }
140
+
141
+ return m, nil
142
+ }
143
+
144
+ func (m model) View() string {
145
+ // Theme Palette
146
+ pink := lipgloss.Color("#FFB7C5") // Sakura Pink
147
+ hotPink := lipgloss.Color("#FF69B4") // Hot Pink
148
+ babyBlue := lipgloss.Color("#89CFF0") // Baby Blue
149
+ lavender := lipgloss.Color("#E6E6FA") // Lavender
150
+ gray := lipgloss.Color("245") // Muted Gray
151
+
152
+ // Layout Styles
153
+ boxStyle := lipgloss.NewStyle().
154
+ Border(lipgloss.RoundedBorder()).
155
+ BorderForeground(pink).
156
+ Padding(1, 2).
157
+ Margin(1, 1)
158
+
159
+ titleStyle := lipgloss.NewStyle().
160
+ Foreground(hotPink).
161
+ Background(lavender).
162
+ Bold(true).
163
+ Padding(0, 1).
164
+ MarginBottom(1)
165
+
166
+ // Animation Frames
167
+ frames := []string{
168
+ "૮ ˶ᵔ ᵕ ᵔ˶ ა", // Happy
169
+ "૮ ˶• ﻌ •˶ ა", // Alert
170
+ "૮ ≧ ﻌ ≦ ა", // Blink
171
+ "૮ / ˶ • ﻌ • ˶ \\ ა", // Paws up
172
+ }
173
+
174
+ // Sparkle Animation
175
+ sparkles := []string{"。・゚✧", "✧・゚。", "。・゚★", "☆・゚。"}
176
+
177
+ // Frame Calculations
178
+ // Slow down the dog animation (every 4th frame)
179
+ idx := (m.frame / 4) % len(frames)
180
+ // Fast sparkles (every 2nd frame)
181
+ sIdx := (m.frame / 2) % len(sparkles)
182
+
183
+ currentDog := frames[idx]
184
+ currentSparkle := sparkles[sIdx]
185
+
186
+ // Spring Animation (Horizontal movement)
187
+ pad := ""
188
+ if m.pos > 0 {
189
+ pad = strings.Repeat(" ", int(m.pos))
190
+ }
191
+
192
+ // Common Elements
193
+ dogRender := lipgloss.NewStyle().Foreground(pink).Render(pad + currentDog + " " + currentSparkle)
194
+
195
+ // Splash Screen Content
196
+ splashContent := lipgloss.JoinVertical(lipgloss.Center,
197
+ titleStyle.Render("♥ Vibepup TUI ♥"),
198
+ "",
199
+ dogRender,
200
+ "",
201
+ lipgloss.NewStyle().Foreground(gray).Render("Loading cuteness..."),
202
+ )
203
+
204
+ switch m.state {
205
+ case stateSplash:
206
+ return boxStyle.Render(splashContent)
207
+
208
+ case stateSetup:
209
+ return boxStyle.Render(
210
+ lipgloss.JoinVertical(lipgloss.Left,
211
+ lipgloss.NewStyle().Foreground(babyBlue).Bold(true).Render("♥ Setup Phase"),
212
+ lipgloss.NewStyle().Foreground(gray).Render("Tip: choose Free setup to auto-configure opencode"),
213
+ "",
214
+ m.form.View(),
215
+ ),
216
+ )
217
+ case stateRunning:
218
+ if m.selected == "new" {
219
+ return boxStyle.Render(
220
+ lipgloss.JoinVertical(lipgloss.Left,
221
+ lipgloss.NewStyle().Foreground(babyBlue).Bold(true).Render("♥ New Project"),
222
+ lipgloss.NewStyle().Foreground(gray).Render("Describe what you want Vibepup to build"),
223
+ "",
224
+ m.newForm.View(),
225
+ ),
226
+ )
227
+ }
228
+ status := lipgloss.NewStyle().Foreground(hotPink).Bold(true).Render("♥ Vibepup is Working ♥")
229
+ mode := lipgloss.NewStyle().Foreground(babyBlue).Render("MODE: " + strings.ToUpper(m.selected))
230
+ spinner := sparkles[m.frame%len(sparkles)]
231
+
232
+ content := lipgloss.JoinVertical(lipgloss.Left,
233
+ status,
234
+ mode,
235
+ "",
236
+ lipgloss.NewStyle().Foreground(gray).Render(spinner+" Running engine... (check output below)"),
237
+ "",
238
+ dogRender,
239
+ )
240
+ return boxStyle.Render(content)
241
+
242
+ case stateDone:
243
+ return boxStyle.Render(
244
+ lipgloss.NewStyle().Foreground(hotPink).Bold(true).Render("♥ All Done! Good Pup! ♥"),
245
+ )
246
+
247
+ default:
248
+ return boxStyle.Render(splashContent)
249
+ }
250
+ }
251
+
252
+ func launchCmd(choice string, args []string) tea.Cmd {
253
+ return func() tea.Msg {
254
+ ctx := context.Background()
255
+ if choice == "watch" {
256
+ args = append([]string{"--watch"}, args...)
257
+ }
258
+ if choice == "run" {
259
+ args = append([]string{"5"}, args...)
260
+ }
261
+ if choice == "new" {
262
+ args = append([]string{"new", "A vibe-coded project"}, args...)
263
+ }
264
+
265
+ log.Info("Starting Vibepup", "args", strings.Join(args, " "))
266
+ cmd := exec.CommandContext(ctx, "bash", "-lc", "vibepup "+strings.Join(args, " "))
267
+ cmd.Stdout = os.Stdout
268
+ cmd.Stderr = os.Stderr
269
+ cmd.Stdin = os.Stdin
270
+ _ = cmd.Run()
271
+ return tea.Quit()
272
+ }
273
+ }
274
+
275
+ func main() {
276
+ p := tea.NewProgram(initialModel())
277
+ if _, err := p.Run(); err != nil {
278
+ fmt.Println("failed to start vibepup tui")
279
+ os.Exit(1)
280
+ }
281
+ }
Binary file