vibepup 1.0.2 → 1.0.3
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/.ralph/runs/iter-0001/agent_response.txt +189 -0
- package/PUBLISHING.md +39 -1
- package/README.md +69 -4
- package/bin/ralph.js +166 -50
- package/lib/ralph-win.js +401 -0
- package/lib/ralph.sh +10 -23
- package/package.json +5 -2
- package/prd.md +4 -0
- package/prd.state.json +1 -0
- package/repo-map.md +0 -0
- package/tui/animations/loaders.go +55 -0
- package/tui/animations/memes.go +35 -0
- package/tui/animations/registry.go +54 -0
- package/tui/config/flags.go +34 -0
- package/tui/go.mod +21 -16
- package/tui/go.sum +42 -34
- package/tui/main.go +269 -198
- package/tui/motion/engine.go +86 -0
- package/tui/persona/quip.go +89 -0
- package/tui/process/runner.go +94 -0
- package/tui/theme/registry.go +74 -0
- package/tui/ui/layout.go +17 -0
- package/tui/ui/statusbar.go +28 -0
- package/tui/ui/viewport.go +63 -0
- package/tui/vibepup-tui +0 -0
- package/vibepup-1.0.3.tgz +0 -0
package/tui/main.go
CHANGED
|
@@ -4,17 +4,52 @@ import (
|
|
|
4
4
|
"context"
|
|
5
5
|
"fmt"
|
|
6
6
|
"os"
|
|
7
|
-
"os/exec"
|
|
8
|
-
"strings"
|
|
9
7
|
"time"
|
|
10
8
|
|
|
9
|
+
"github.com/charmbracelet/bubbles/help"
|
|
10
|
+
"github.com/charmbracelet/bubbles/key"
|
|
11
|
+
"github.com/charmbracelet/bubbles/spinner"
|
|
11
12
|
tea "github.com/charmbracelet/bubbletea"
|
|
12
|
-
"github.com/charmbracelet/harmonica"
|
|
13
13
|
"github.com/charmbracelet/huh"
|
|
14
14
|
"github.com/charmbracelet/lipgloss"
|
|
15
|
-
"github.com/
|
|
15
|
+
"github.com/mattn/go-isatty"
|
|
16
|
+
|
|
17
|
+
"vibepup-tui/config"
|
|
18
|
+
"vibepup-tui/motion"
|
|
19
|
+
"vibepup-tui/persona"
|
|
20
|
+
"vibepup-tui/process"
|
|
21
|
+
"vibepup-tui/theme"
|
|
22
|
+
"vibepup-tui/ui"
|
|
16
23
|
)
|
|
17
24
|
|
|
25
|
+
// --- Key Bindings ---
|
|
26
|
+
|
|
27
|
+
type KeyMap struct {
|
|
28
|
+
Quit key.Binding
|
|
29
|
+
Help key.Binding
|
|
30
|
+
NextTheme key.Binding
|
|
31
|
+
Pet key.Binding
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func DefaultKeyMap() KeyMap {
|
|
35
|
+
return KeyMap{
|
|
36
|
+
Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")),
|
|
37
|
+
Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "help")),
|
|
38
|
+
NextTheme: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "theme")),
|
|
39
|
+
Pet: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "pet dog")),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func (k KeyMap) ShortHelp() []key.Binding {
|
|
44
|
+
return []key.Binding{k.Help, k.Quit, k.NextTheme, k.Pet}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func (k KeyMap) FullHelp() [][]key.Binding {
|
|
48
|
+
return [][]key.Binding{{k.Help, k.Quit, k.NextTheme, k.Pet}}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- Model ---
|
|
52
|
+
|
|
18
53
|
type viewState int
|
|
19
54
|
|
|
20
55
|
const (
|
|
@@ -25,257 +60,293 @@ const (
|
|
|
25
60
|
)
|
|
26
61
|
|
|
27
62
|
type model struct {
|
|
28
|
-
state
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
state viewState
|
|
64
|
+
width int
|
|
65
|
+
height int
|
|
66
|
+
ready bool
|
|
67
|
+
|
|
68
|
+
// Components
|
|
69
|
+
keys KeyMap
|
|
70
|
+
help help.Model
|
|
71
|
+
form *huh.Form
|
|
72
|
+
newForm *huh.Form
|
|
73
|
+
viewport ui.LogViewport
|
|
74
|
+
spinner spinner.Model
|
|
75
|
+
|
|
76
|
+
// Config & State
|
|
77
|
+
flags config.Flags
|
|
78
|
+
theme theme.Theme
|
|
79
|
+
styles lipgloss.Style // Simplified, use theme package directly where possible
|
|
80
|
+
snark persona.SnarkLevel
|
|
81
|
+
|
|
82
|
+
// Process
|
|
83
|
+
runner *process.Runner
|
|
84
|
+
selected string
|
|
85
|
+
newIdea string
|
|
86
|
+
args []string
|
|
87
|
+
|
|
88
|
+
// Animation
|
|
89
|
+
motion motion.Engine
|
|
90
|
+
frame int
|
|
91
|
+
dogState string // "sleeping", "running", "barking", "happy"
|
|
40
92
|
}
|
|
41
93
|
|
|
42
|
-
func initialModel() model {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
94
|
+
func initialModel(flags config.Flags) model {
|
|
95
|
+
th := theme.Get(flags.Theme)
|
|
96
|
+
snark := persona.ParseSnark(flags.Snark)
|
|
97
|
+
|
|
98
|
+
s := spinner.New()
|
|
99
|
+
s.Spinner = spinner.Points // More modern spinner
|
|
100
|
+
s.Style = lipgloss.NewStyle().Foreground(th.Highlight)
|
|
47
101
|
|
|
48
|
-
|
|
102
|
+
m := model{
|
|
103
|
+
state: stateSplash,
|
|
104
|
+
keys: DefaultKeyMap(),
|
|
105
|
+
help: help.New(),
|
|
106
|
+
flags: flags,
|
|
107
|
+
theme: th,
|
|
108
|
+
snark: snark,
|
|
109
|
+
motion: motion.New(flags.PerfLow, flags.Quiet),
|
|
110
|
+
spinner: s,
|
|
111
|
+
dogState: "sleeping",
|
|
112
|
+
selected: "watch", // Default
|
|
113
|
+
args: os.Args[1:],
|
|
114
|
+
}
|
|
49
115
|
|
|
50
|
-
|
|
116
|
+
// Setup Form
|
|
51
117
|
m.form = huh.NewForm(
|
|
52
118
|
huh.NewGroup(
|
|
53
119
|
huh.NewSelect[string]().
|
|
54
|
-
Title("♥
|
|
120
|
+
Title("♥ Pick your poison ♥").
|
|
55
121
|
Options(
|
|
56
|
-
huh.NewOption("Watch (
|
|
57
|
-
huh.NewOption("Run 5
|
|
58
|
-
huh.NewOption("New
|
|
59
|
-
huh.NewOption("Free
|
|
122
|
+
huh.NewOption("Watch Mode (Stalker vibes)", "watch"),
|
|
123
|
+
huh.NewOption("Run 5 Loops (Quickie)", "run"),
|
|
124
|
+
huh.NewOption("New Project (YOLO)", "new"),
|
|
125
|
+
huh.NewOption("Free Setup (Broke af)", "free"),
|
|
60
126
|
).
|
|
61
127
|
Value(&m.selected),
|
|
62
128
|
),
|
|
63
|
-
)
|
|
129
|
+
).WithTheme(huh.ThemeDracula())
|
|
130
|
+
|
|
64
131
|
m.newForm = huh.NewForm(
|
|
65
132
|
huh.NewGroup(
|
|
66
133
|
huh.NewInput().
|
|
67
|
-
Title("
|
|
68
|
-
Prompt("
|
|
69
|
-
Placeholder("
|
|
134
|
+
Title("Spill the tea ☕").
|
|
135
|
+
Prompt("Manifest: ").
|
|
136
|
+
Placeholder("Make me a unicorn...").
|
|
70
137
|
Value(&m.newIdea),
|
|
71
138
|
),
|
|
72
|
-
)
|
|
139
|
+
).WithTheme(huh.ThemeDracula())
|
|
73
140
|
|
|
74
141
|
return m
|
|
75
142
|
}
|
|
76
143
|
|
|
77
144
|
func (m model) Init() tea.Cmd {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
145
|
+
return tea.Batch(
|
|
146
|
+
m.motion.Next(),
|
|
147
|
+
m.spinner.Tick,
|
|
148
|
+
)
|
|
82
149
|
}
|
|
83
150
|
|
|
84
151
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
152
|
+
var cmds []tea.Cmd
|
|
153
|
+
var cmd tea.Cmd
|
|
154
|
+
|
|
85
155
|
switch msg := msg.(type) {
|
|
156
|
+
case tea.WindowSizeMsg:
|
|
157
|
+
m.width = msg.Width
|
|
158
|
+
m.height = msg.Height
|
|
159
|
+
m.help.Width = msg.Width
|
|
160
|
+
|
|
161
|
+
// Dynamic Layout Calculation
|
|
162
|
+
headerHeight := 10 // Approximation, should be measured
|
|
163
|
+
footerHeight := 3
|
|
164
|
+
vpHeight := msg.Height - headerHeight - footerHeight - 2 // Borders
|
|
165
|
+
|
|
166
|
+
if !m.ready {
|
|
167
|
+
m.viewport = ui.NewLogViewport(msg.Width-4, vpHeight)
|
|
168
|
+
m.ready = true
|
|
169
|
+
} else {
|
|
170
|
+
m.viewport.SetSize(msg.Width-4, vpHeight)
|
|
171
|
+
}
|
|
172
|
+
|
|
86
173
|
case tea.KeyMsg:
|
|
87
|
-
if
|
|
174
|
+
if m.state == stateRunning && m.runner != nil {
|
|
175
|
+
// Pass interactions to viewport if needed
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
switch {
|
|
179
|
+
case key.Matches(msg, m.keys.Quit):
|
|
180
|
+
if m.runner != nil {
|
|
181
|
+
m.runner.Kill() // ZOMBIE KILLER
|
|
182
|
+
}
|
|
88
183
|
return m, tea.Quit
|
|
184
|
+
case key.Matches(msg, m.keys.Pet):
|
|
185
|
+
m.dogState = "happy"
|
|
186
|
+
cmds = append(cmds, tea.Tick(time.Second, func(t time.Time) tea.Msg {
|
|
187
|
+
return "dog_reset"
|
|
188
|
+
}))
|
|
89
189
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
m.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
190
|
+
|
|
191
|
+
case string:
|
|
192
|
+
if msg == "dog_reset" {
|
|
193
|
+
if m.runner != nil {
|
|
194
|
+
m.dogState = "running"
|
|
195
|
+
} else {
|
|
196
|
+
m.dogState = "sleeping"
|
|
97
197
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case motion.TickMsg:
|
|
201
|
+
m.frame++
|
|
202
|
+
cmds = append(cmds, m.motion.Next())
|
|
203
|
+
|
|
204
|
+
case spinner.TickMsg:
|
|
205
|
+
m.spinner, cmd = m.spinner.Update(msg)
|
|
206
|
+
cmds = append(cmds, cmd)
|
|
207
|
+
|
|
208
|
+
case process.OutputMsg:
|
|
209
|
+
m.viewport.WriteLine(string(msg))
|
|
210
|
+
cmds = append(cmds, m.runner.WaitForOutput())
|
|
211
|
+
|
|
212
|
+
case process.DoneMsg:
|
|
213
|
+
m.dogState = "sleeping"
|
|
214
|
+
m.viewport.WriteLine("\n--- Process Finished ---")
|
|
215
|
+
if msg.Err != nil {
|
|
216
|
+
m.viewport.WriteLine(fmt.Sprintf("Error: %v", msg.Err))
|
|
217
|
+
m.dogState = "barking"
|
|
218
|
+
}
|
|
219
|
+
m.runner = nil
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Handle Forms
|
|
223
|
+
if m.state == stateSetup {
|
|
224
|
+
form, cmd := m.form.Update(msg)
|
|
225
|
+
if f, ok := form.(*huh.Form); ok {
|
|
226
|
+
m.form = f
|
|
227
|
+
if m.form.State == huh.StateCompleted {
|
|
228
|
+
if m.selected == "new" {
|
|
229
|
+
m.state = stateRunning
|
|
230
|
+
cmds = append(cmds, m.newForm.Init())
|
|
104
231
|
} else {
|
|
105
|
-
m.
|
|
232
|
+
m.state = stateRunning
|
|
233
|
+
cmds = append(cmds, m.startProcess())
|
|
106
234
|
}
|
|
107
235
|
}
|
|
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
236
|
}
|
|
237
|
+
cmds = append(cmds, cmd)
|
|
111
238
|
}
|
|
112
239
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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)
|
|
240
|
+
if m.state == stateRunning && m.selected == "new" && m.runner == nil {
|
|
241
|
+
form, cmd := m.newForm.Update(msg)
|
|
242
|
+
if f, ok := form.(*huh.Form); ok {
|
|
243
|
+
m.newForm = f
|
|
130
244
|
if m.newForm.State == huh.StateCompleted {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
idea = "A vibe-coded project"
|
|
134
|
-
}
|
|
135
|
-
return m, launchCmd("new", append([]string{idea}, m.args...))
|
|
245
|
+
m.args = append(m.args, "new", m.newIdea)
|
|
246
|
+
cmds = append(cmds, m.startProcess())
|
|
136
247
|
}
|
|
137
|
-
return m, cmd
|
|
138
248
|
}
|
|
249
|
+
cmds = append(cmds, cmd)
|
|
139
250
|
}
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
251
|
+
|
|
252
|
+
// Auto-advance splash
|
|
253
|
+
if m.state == stateSplash && m.frame > 100 {
|
|
254
|
+
m.state = stateSetup
|
|
172
255
|
}
|
|
173
256
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
idx := (m.frame / 4) % len(frames)
|
|
180
|
-
// Fast sparkles (every 2nd frame)
|
|
181
|
-
sIdx := (m.frame / 2) % len(sparkles)
|
|
257
|
+
// Update viewport
|
|
258
|
+
if m.ready {
|
|
259
|
+
m.viewport, cmd = m.viewport.Update(msg)
|
|
260
|
+
cmds = append(cmds, cmd)
|
|
261
|
+
}
|
|
182
262
|
|
|
183
|
-
|
|
184
|
-
|
|
263
|
+
return m, tea.Batch(cmds...)
|
|
264
|
+
}
|
|
185
265
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
266
|
+
func (m *model) startProcess() tea.Cmd {
|
|
267
|
+
if !m.flags.ForceRun && !isatty.IsTerminal(os.Stdout.Fd()) {
|
|
268
|
+
m.viewport.WriteLine("Error: Not a TTY. Use --force-run.")
|
|
269
|
+
return nil
|
|
190
270
|
}
|
|
191
271
|
|
|
192
|
-
|
|
193
|
-
|
|
272
|
+
args := m.args
|
|
273
|
+
if m.selected == "watch" {
|
|
274
|
+
args = append([]string{"--watch"}, args...)
|
|
275
|
+
} else if m.selected == "run" {
|
|
276
|
+
args = append([]string{"5"}, args...)
|
|
277
|
+
} else if m.selected == "free" {
|
|
278
|
+
args = []string{"free"}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Actually invoke the CLI (ralph.js -> ralph.sh mechanism, but we call 'vibepup' assuming it's in path or we call the shell script directly)
|
|
282
|
+
// For local dev, we might need to call the script directly if 'vibepup' isn't in PATH.
|
|
283
|
+
// But let's assume 'vibepup' is the command.
|
|
284
|
+
runCmd := "vibepup"
|
|
285
|
+
if m.flags.Runner != "" {
|
|
286
|
+
runCmd = m.flags.Runner
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// If running locally from repo, we might want to call the script directly?
|
|
290
|
+
// The user said "run this project from the build".
|
|
291
|
+
// We'll stick to "vibepup" and assume it's linked or we can use absolute path if needed.
|
|
292
|
+
// Let's use the first arg as the command if provided, or default to "vibepup"
|
|
293
|
+
|
|
294
|
+
m.dogState = "running"
|
|
295
|
+
m.viewport.WriteLine("--- Starting Vibepup ---")
|
|
296
|
+
|
|
297
|
+
var cmd tea.Cmd
|
|
298
|
+
m.runner, cmd = process.Start(context.Background(), runCmd, args)
|
|
299
|
+
|
|
300
|
+
return tea.Batch(cmd, m.runner.WaitForOutput())
|
|
301
|
+
}
|
|
194
302
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
dogRender,
|
|
200
|
-
"",
|
|
201
|
-
lipgloss.NewStyle().Foreground(gray).Render("Loading cuteness..."),
|
|
202
|
-
)
|
|
303
|
+
func (m model) View() string {
|
|
304
|
+
if !m.ready {
|
|
305
|
+
return "Initializing..."
|
|
306
|
+
}
|
|
203
307
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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(),
|
|
308
|
+
// 1. Splash
|
|
309
|
+
if m.state == stateSplash {
|
|
310
|
+
return ui.BoxStyle.Render(
|
|
311
|
+
lipgloss.JoinVertical(lipgloss.Center,
|
|
312
|
+
lipgloss.NewStyle().Foreground(m.theme.Accent).Render("♥ VIBEPUP TUI ♥"),
|
|
313
|
+
"\nLoading Vibes...\n",
|
|
314
|
+
m.spinner.View(),
|
|
215
315
|
),
|
|
216
316
|
)
|
|
217
|
-
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 2. Form
|
|
320
|
+
if m.state == stateSetup || (m.state == stateRunning && m.runner == nil && m.selected == "new" && m.newForm.State != huh.StateCompleted) {
|
|
321
|
+
form := m.form.View()
|
|
218
322
|
if m.selected == "new" {
|
|
219
|
-
|
|
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
|
-
)
|
|
323
|
+
form = m.newForm.View()
|
|
227
324
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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)
|
|
325
|
+
return ui.BoxStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
|
|
326
|
+
lipgloss.NewStyle().Foreground(m.theme.Accent).Render("SETUP"),
|
|
327
|
+
form,
|
|
328
|
+
))
|
|
249
329
|
}
|
|
250
|
-
}
|
|
251
330
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
}
|
|
331
|
+
// 3. Running
|
|
332
|
+
header := lipgloss.JoinVertical(lipgloss.Left,
|
|
333
|
+
lipgloss.NewStyle().Foreground(m.theme.Highlight).Render("♥ "+persona.GetStatus(m.selected, m.snark)+" ♥ "+m.spinner.View()),
|
|
334
|
+
motion.GetDogFrame(m.dogState, m.frame),
|
|
335
|
+
persona.RandomQuip(m.snark),
|
|
336
|
+
)
|
|
264
337
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
_ = cmd.Run()
|
|
271
|
-
return tea.Quit()
|
|
272
|
-
}
|
|
338
|
+
return ui.BoxStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
|
|
339
|
+
header,
|
|
340
|
+
m.viewport.View(),
|
|
341
|
+
m.help.View(m.keys),
|
|
342
|
+
))
|
|
273
343
|
}
|
|
274
344
|
|
|
275
345
|
func main() {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
346
|
+
flags := config.Parse()
|
|
347
|
+
m := initialModel(flags)
|
|
348
|
+
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
|
|
349
|
+
fmt.Println("Error:", err)
|
|
279
350
|
os.Exit(1)
|
|
280
351
|
}
|
|
281
352
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package motion
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
|
|
6
|
+
tea "github.com/charmbracelet/bubbletea"
|
|
7
|
+
"github.com/Nomadcxx/sysc-Go/animations"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type TickMsg time.Time
|
|
11
|
+
|
|
12
|
+
type Engine struct {
|
|
13
|
+
Quiet bool
|
|
14
|
+
LowPerf bool
|
|
15
|
+
Effect animations.Animation
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func New(lowPerf, quiet bool) Engine {
|
|
19
|
+
// Initialize default effect (Matrix Rain)
|
|
20
|
+
// sysc-Go animations usually need initialization
|
|
21
|
+
// For now, we'll just placeholder it or use a simple one if available
|
|
22
|
+
return Engine{
|
|
23
|
+
Quiet: quiet,
|
|
24
|
+
LowPerf: lowPerf,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (e Engine) Next() tea.Cmd {
|
|
29
|
+
d := time.Millisecond * 1000 / 60
|
|
30
|
+
if e.LowPerf {
|
|
31
|
+
d = time.Millisecond * 1000 / 15
|
|
32
|
+
}
|
|
33
|
+
return tea.Tick(d, func(t time.Time) tea.Msg {
|
|
34
|
+
return TickMsg(t)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ASCII Dog Sprites
|
|
39
|
+
var DogSleeping = []string{
|
|
40
|
+
" z",
|
|
41
|
+
" z ",
|
|
42
|
+
" Z ",
|
|
43
|
+
"૮ – ﻌ – ა",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var DogRunning = []string{
|
|
47
|
+
" ",
|
|
48
|
+
" ",
|
|
49
|
+
" ",
|
|
50
|
+
"૮ ˶ᵔ ᵕ ᵔ˶ ა", // Frame 1
|
|
51
|
+
" ",
|
|
52
|
+
" ",
|
|
53
|
+
" ",
|
|
54
|
+
"૮ ˶• ﻌ •˶ ა", // Frame 2
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var DogBarking = []string{
|
|
58
|
+
" WOOF! ",
|
|
59
|
+
" ",
|
|
60
|
+
" ",
|
|
61
|
+
"૮ ≧ ﻌ ≦ ა",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// GetDogFrame returns the current frame for the dog state
|
|
65
|
+
func GetDogFrame(state string, frame int) string {
|
|
66
|
+
switch state {
|
|
67
|
+
case "sleeping":
|
|
68
|
+
// Animate Zzz
|
|
69
|
+
zzz := []string{"", "z", "zz", "zzz"}
|
|
70
|
+
return " " + zzz[(frame/30)%4] + "\n" + DogSleeping[3]
|
|
71
|
+
case "barking":
|
|
72
|
+
if (frame/10)%2 == 0 {
|
|
73
|
+
return DogBarking[0] + "\n" + DogBarking[3]
|
|
74
|
+
}
|
|
75
|
+
return " \n" + DogBarking[3]
|
|
76
|
+
case "happy":
|
|
77
|
+
// Jump animation
|
|
78
|
+
if (frame/5)%2 == 0 {
|
|
79
|
+
return " \n" + "૮ ˶ᵔ ᵕ ᵔ˶ ა" // Up
|
|
80
|
+
}
|
|
81
|
+
return " \n" + "૮ ˶• ﻌ •˶ ა" // Down
|
|
82
|
+
default: // Running
|
|
83
|
+
frames := []string{"૮ ˶ᵔ ᵕ ᵔ˶ ა", "૮ ˶• ﻌ •˶ ა"}
|
|
84
|
+
return " \n" + frames[(frame/10)%len(frames)]
|
|
85
|
+
}
|
|
86
|
+
}
|