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 +47 -1
- package/bin/ralph.js +40 -10
- package/lib/ralph.sh +68 -2
- package/package.json +4 -2
- package/tui/go.mod +37 -0
- package/tui/go.sum +69 -0
- package/tui/main.go +281 -0
- package/tui/vibepup-tui +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **"Fetch Code. Sit. Stay. Good Pup."**
|
|
4
4
|
|
|
5
|
+

|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A loyal, DX-first split-brain agent harness with cyberpunk vibes.",
|
|
5
|
-
"bin":
|
|
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
|
+
}
|
package/tui/vibepup-tui
ADDED
|
Binary file
|