vibepup 1.0.0 → 1.0.1

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,11 @@ 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
+
52
60
  ### 2. Fetch!
53
61
  Go to any empty folder and tell Vibepup what to build.
54
62
 
@@ -79,8 +87,35 @@ vibepup --watch
79
87
  ```
80
88
  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
89
 
90
+ ### 4. TUI Mode
91
+ ```bash
92
+ vibepup --tui
93
+ ```
94
+ TUI mode provides a Bubble Tea interface with puppy animation, quick mode selection, and a clean launch experience.
95
+
82
96
  ## ⚙️ Configuration
83
- Vibepup works out of the box, but you can tune his brain in `~/.config/ralph/config.json`:
97
+ Vibepup works out of the box. 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:
98
+
99
+ ```bash
100
+ npm install -g opencode-antigravity-auth
101
+ opencode auth login antigravity
102
+ ```
103
+
104
+ If you cannot open a browser on the target machine:
105
+ ```bash
106
+ opencode auth print-token antigravity
107
+ export OPENCODE_ANTIGRAVITY_TOKEN="<token>"
108
+ ```
109
+
110
+ ### TUI build (optional)
111
+ TUI requires Go 1.22+.
112
+ ```bash
113
+ cd npm-package/tui
114
+
115
+ go build -o vibepup-tui
116
+ ```
117
+
118
+ Vibepup also supports manual config in `~/.config/ralph/config.json`:
84
119
 
85
120
  ```json
86
121
  {
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
@@ -85,6 +85,28 @@ echo "🐾 Vibepup v1.0 (CLI Mode)"
85
85
  echo " Engine: $ENGINE_DIR"
86
86
  echo " Context: $PROJECT_DIR"
87
87
 
88
+ type -p opencode >/dev/null 2>&1 || {
89
+ if command -v curl >/dev/null 2>&1; then
90
+ UNAME=$(uname -s)
91
+ if [ "$UNAME" = "Linux" ] || [ "$UNAME" = "Darwin" ]; then
92
+ echo "⚠️ opencode not found. Installing..."
93
+ curl -fsSL https://opencode.ai/install | bash || true
94
+ fi
95
+ fi
96
+ }
97
+
98
+ if ! command -v opencode >/dev/null 2>&1; then
99
+ echo "❌ opencode not found. Vibepup requires opencode to run."
100
+ echo " Install with one of:"
101
+ echo " - curl -fsSL https://opencode.ai/install | bash"
102
+ echo " - npm install -g opencode-ai"
103
+ echo " - brew install anomalyco/tap/opencode"
104
+ echo " Free-tier option:"
105
+ echo " - npm install -g opencode-antigravity-auth"
106
+ echo " - opencode auth login antigravity"
107
+ exit 127
108
+ fi
109
+
88
110
  # --- Smart Model Discovery ---
89
111
  get_available_models() {
90
112
  local PREF_MODELS=("$@")
@@ -92,7 +114,8 @@ get_available_models() {
92
114
 
93
115
  echo "🔍 Verifying available models..." >&2
94
116
 
95
- local ALL_MODELS=$(opencode models --refresh | grep -E "^[a-z0-9-]+\/[a-z0-9.-]+" || true)
117
+ local ALL_MODELS
118
+ ALL_MODELS=$(opencode models --refresh 2>/dev/null | grep -E "^[a-z0-9-]+/[a-z0-9.-]+" || true)
96
119
 
97
120
  for PREF in "${PREF_MODELS[@]}"; do
98
121
  if echo "$ALL_MODELS" | grep -q "^$PREF$"; then
@@ -105,6 +128,11 @@ get_available_models() {
105
128
  AVAILABLE_MODELS+=($(echo "$ALL_MODELS" | grep "gpt-4o" | head -n 1))
106
129
  AVAILABLE_MODELS+=($(echo "$ALL_MODELS" | grep "claude-sonnet" | head -n 1))
107
130
  fi
131
+
132
+ if [ ${#AVAILABLE_MODELS[@]} -eq 0 ]; then
133
+ AVAILABLE_MODELS=("opencode/grok-code")
134
+ echo "⚠️ Using fallback model: opencode/grok-code" >&2
135
+ fi
108
136
 
109
137
  echo "${AVAILABLE_MODELS[@]}"
110
138
  }
@@ -265,7 +293,6 @@ run_agent() {
265
293
  --file "$PROJECT_DIR/repo-map.md" \
266
294
  --file "$ITER_DIR/progress.tail.log" \
267
295
  "${EXTRA_ARGS[@]}" \
268
- --agent general \
269
296
  --model "$MODEL"
270
297
  }
271
298
 
@@ -316,6 +343,11 @@ while true; do
316
343
  set -e
317
344
 
318
345
  RESPONSE=$(cat "$ITER_DIR/agent_response.txt")
346
+
347
+ if echo "$RESPONSE" | grep -qi "not supported\|ModelNotFoundError\|Make sure the model is enabled"; then
348
+ echo " ⚠️ Model $MODEL not supported. Falling back..."
349
+ continue
350
+ fi
319
351
 
320
352
  if [ $EXIT_CODE -eq 0 ] && [ -n "$RESPONSE" ]; then
321
353
  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.1",
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,243 @@
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
+ selected string
34
+ spring harmonica.Spring
35
+ pos float64
36
+ velocity float64
37
+ targetPos float64
38
+ }
39
+
40
+ func initialModel() model {
41
+ args := []string{"--watch"}
42
+ if len(os.Args) > 1 {
43
+ args = os.Args[1:]
44
+ }
45
+
46
+ spring := harmonica.NewSpring(harmonica.FPS(60), 6.0, 0.2)
47
+
48
+ m := model{state: stateSplash, start: time.Now(), args: args, selected: "watch", spring: spring, targetPos: 10}
49
+ m.form = huh.NewForm(
50
+ huh.NewGroup(
51
+ huh.NewSelect[string]().
52
+ Title("♥ What shall we do today? ♥").
53
+ Options(
54
+ huh.NewOption("Watch (recommended)", "watch"),
55
+ huh.NewOption("Run 5 iterations", "run"),
56
+ huh.NewOption("New project from idea", "new"),
57
+ ).
58
+ Value(&m.selected),
59
+ ),
60
+ )
61
+
62
+ return m
63
+ }
64
+
65
+ func (m model) Init() tea.Cmd {
66
+ log.SetLevel(log.InfoLevel)
67
+ log.SetReportCaller(false)
68
+ log.SetTimeFormat("")
69
+ return tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
70
+ }
71
+
72
+ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
73
+ switch msg := msg.(type) {
74
+ case time.Time:
75
+ switch m.state {
76
+ case stateSplash:
77
+ m.frame++
78
+ if time.Since(m.start) > time.Second*2 {
79
+ m.state = stateSetup
80
+ return m, m.form.Init()
81
+ }
82
+ return m, tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
83
+ case stateRunning:
84
+ m.frame++
85
+ if m.frame%20 == 0 {
86
+ if m.targetPos == 0 {
87
+ m.targetPos = 10
88
+ } else {
89
+ m.targetPos = 0
90
+ }
91
+ }
92
+ m.pos, m.velocity = m.spring.Update(m.pos, m.velocity, m.targetPos)
93
+ return m, tea.Tick(time.Millisecond*80, func(t time.Time) tea.Msg { return t })
94
+ }
95
+ case tea.KeyMsg:
96
+ if m.state == stateSetup {
97
+ var cmd tea.Cmd
98
+ fm, cmd := m.form.Update(msg)
99
+ m.form = fm.(*huh.Form)
100
+ if m.form.State == huh.StateCompleted {
101
+ m.state = stateRunning
102
+ return m, launchCmd(m.selected, m.args)
103
+ }
104
+ return m, cmd
105
+ }
106
+ }
107
+
108
+ if m.state == stateSetup {
109
+ fm, cmd := m.form.Update(msg)
110
+ m.form = fm.(*huh.Form)
111
+ return m, cmd
112
+ }
113
+ return m, nil
114
+ }
115
+
116
+ func (m model) View() string {
117
+ // Theme Palette
118
+ pink := lipgloss.Color("#FFB7C5") // Sakura Pink
119
+ hotPink := lipgloss.Color("#FF69B4") // Hot Pink
120
+ babyBlue := lipgloss.Color("#89CFF0") // Baby Blue
121
+ lavender := lipgloss.Color("#E6E6FA") // Lavender
122
+ gray := lipgloss.Color("245") // Muted Gray
123
+
124
+ // Layout Styles
125
+ boxStyle := lipgloss.NewStyle().
126
+ Border(lipgloss.RoundedBorder()).
127
+ BorderForeground(pink).
128
+ Padding(1, 2).
129
+ Margin(1, 1)
130
+
131
+ titleStyle := lipgloss.NewStyle().
132
+ Foreground(hotPink).
133
+ Background(lavender).
134
+ Bold(true).
135
+ Padding(0, 1).
136
+ MarginBottom(1)
137
+
138
+ // Animation Frames
139
+ frames := []string{
140
+ "૮ ˶ᵔ ᵕ ᵔ˶ ა", // Happy
141
+ "૮ ˶• ﻌ •˶ ა", // Alert
142
+ "૮ ≧ ﻌ ≦ ა", // Blink
143
+ "૮ / ˶ • ﻌ • ˶ \\ ა", // Paws up
144
+ }
145
+
146
+ // Sparkle Animation
147
+ sparkles := []string{"。・゚✧", "✧・゚。", "。・゚★", "☆・゚。"}
148
+
149
+ // Frame Calculations
150
+ // Slow down the dog animation (every 4th frame)
151
+ idx := (m.frame / 4) % len(frames)
152
+ // Fast sparkles (every 2nd frame)
153
+ sIdx := (m.frame / 2) % len(sparkles)
154
+
155
+ currentDog := frames[idx]
156
+ currentSparkle := sparkles[sIdx]
157
+
158
+ // Spring Animation (Horizontal movement)
159
+ pad := ""
160
+ if m.pos > 0 {
161
+ pad = strings.Repeat(" ", int(m.pos))
162
+ }
163
+
164
+ // Common Elements
165
+ dogRender := lipgloss.NewStyle().Foreground(pink).Render(pad + currentDog + " " + currentSparkle)
166
+
167
+ // Splash Screen Content
168
+ splashContent := lipgloss.JoinVertical(lipgloss.Center,
169
+ titleStyle.Render("♥ Vibepup TUI ♥"),
170
+ "",
171
+ dogRender,
172
+ "",
173
+ lipgloss.NewStyle().Foreground(gray).Render("Loading cuteness..."),
174
+ )
175
+
176
+ switch m.state {
177
+ case stateSplash:
178
+ return boxStyle.Render(splashContent)
179
+
180
+ case stateSetup:
181
+ return boxStyle.Render(
182
+ lipgloss.JoinVertical(lipgloss.Left,
183
+ lipgloss.NewStyle().Foreground(babyBlue).Bold(true).Render("♥ Setup Phase"),
184
+ "",
185
+ m.form.View(),
186
+ ),
187
+ )
188
+
189
+ case stateRunning:
190
+ status := lipgloss.NewStyle().Foreground(hotPink).Bold(true).Render("♥ Vibepup is Working ♥")
191
+ mode := lipgloss.NewStyle().Foreground(babyBlue).Render("MODE: " + strings.ToUpper(m.selected))
192
+ spinner := sparkles[m.frame%len(sparkles)]
193
+
194
+ content := lipgloss.JoinVertical(lipgloss.Left,
195
+ status,
196
+ mode,
197
+ "",
198
+ lipgloss.NewStyle().Foreground(gray).Render(spinner+" Running engine... (check output below)"),
199
+ "",
200
+ dogRender,
201
+ )
202
+ return boxStyle.Render(content)
203
+
204
+ case stateDone:
205
+ return boxStyle.Render(
206
+ lipgloss.NewStyle().Foreground(hotPink).Bold(true).Render("♥ All Done! Good Pup! ♥"),
207
+ )
208
+
209
+ default:
210
+ return boxStyle.Render(splashContent)
211
+ }
212
+ }
213
+
214
+ func launchCmd(choice string, args []string) tea.Cmd {
215
+ return func() tea.Msg {
216
+ ctx := context.Background()
217
+ if choice == "watch" {
218
+ args = append([]string{"--watch"}, args...)
219
+ }
220
+ if choice == "run" {
221
+ args = append([]string{"5"}, args...)
222
+ }
223
+ if choice == "new" {
224
+ args = append([]string{"new", "A vibe-coded project"}, args...)
225
+ }
226
+
227
+ log.Info("Starting Vibepup", "args", strings.Join(args, " "))
228
+ cmd := exec.CommandContext(ctx, "bash", "-lc", "vibepup "+strings.Join(args, " "))
229
+ cmd.Stdout = os.Stdout
230
+ cmd.Stderr = os.Stderr
231
+ cmd.Stdin = os.Stdin
232
+ _ = cmd.Run()
233
+ return tea.Quit()
234
+ }
235
+ }
236
+
237
+ func main() {
238
+ p := tea.NewProgram(initialModel())
239
+ if _, err := p.Run(); err != nil {
240
+ fmt.Println("failed to start vibepup tui")
241
+ os.Exit(1)
242
+ }
243
+ }
Binary file