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 +36 -1
- package/bin/ralph.js +40 -10
- package/lib/ralph.sh +34 -2
- package/package.json +4 -2
- package/tui/go.mod +37 -0
- package/tui/go.sum +69 -0
- package/tui/main.go +243 -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,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,
|
|
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
|
|
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
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "1.0.1",
|
|
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,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
|
+
}
|
package/tui/vibepup-tui
ADDED
|
Binary file
|