Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ Kernel includes a suite of instant commands accessible directly from the input b
| `/model <id>` | Dynamically switch models (e.g. `/model gemini-3.5-flash`, `/model gpt-4o`) |
| `/web <query>` | Performs a zero-key live web search and returns markdown summaries |
| `/read <path>` | Reads and displays syntax-highlighted code from any project file |
| `/theme [name]` | Dynamically switch color themes (`cyberpunk`, `matrix`, `dracula`, `nord`, `tokyo`, `synthwave`) |
| `/commit [-y]` | AI Git Conventional Commit message generator & 1-click committer |
| `/save [filename]` | Exports the entire chat session to a formatted `.md` file |
| `$ <cmd>` or `/exec <cmd>` | Executes a local shell command and embeds stdout/stderr in chat |
| `:q`, `/quit`, `exit` | Saves conversation to history and cleanly exits |
Expand Down
Binary file added internal/audio/assets/cat_meow.wav
Binary file not shown.
Binary file added internal/audio/assets/dog_bark.wav
Binary file not shown.
75 changes: 34 additions & 41 deletions internal/audio/audio.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,63 @@ package audio

import (
"context"
_ "embed"
"fmt"
"os/exec"
"runtime"
"strings"
"time"
)

//go:embed assets/dog_bark.wav
var dogBarkWAV []byte

//go:embed assets/cat_meow.wav
var catMeowWAV []byte

var (
VoiceEnabled = true
)

// PlayCompanionSound plays an animal-specific vocalization or sci-fi sound effect
// PlayCompanionSound plays an authentic animal vocalization or sci-fi sound effect
func PlayCompanionSound(skin string) {
if !VoiceEnabled {
return
}

skin = strings.ToLower(skin)

go func() {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()

if runtime.GOOS == "windows" {
var script string
switch skin {
case "cat":
// Cat cute meow vocalization
script = `(New-Object -ComObject SAPI.SpVoice).Speak('Meow!')`
case "dragon":
// Dragon roar
script = `[console]::beep(200, 150); (New-Object -ComObject SAPI.SpVoice).Speak('Rawr!')`
case "drone":
// Futuristic digital sonar chirp sequence
script = `[console]::beep(1800, 40); [console]::beep(2400, 60); [console]::beep(3200, 80)`
default: // "dog"
// Dog enthusiastic Woof vocalization
script = `(New-Object -ComObject SAPI.SpVoice).Speak('Woof! Woof!')`
}
cmd := exec.CommandContext(ctx, "powershell", "-NoProfile", "-NonInteractive", "-Command", script)
_ = cmd.Run()
} else if runtime.GOOS == "darwin" {
switch skin {
case "cat":
cmd := exec.CommandContext(ctx, "say", "-v", "Victoria", "Meow")
_ = cmd.Run()
case "dragon":
cmd := exec.CommandContext(ctx, "say", "-v", "Zarvox", "Rawr")
_ = cmd.Run()
case "drone":
cmd := exec.CommandContext(ctx, "afplay", "/System/Library/Sounds/Submarine.aiff")
switch skin {
case "dog":
playWAV(dogBarkWAV)
case "cat":
playWAV(catMeowWAV)
case "drone":
go func() {
if runtime.GOOS == "windows" {
script := `[console]::beep(1800, 40); [console]::beep(2400, 60); [console]::beep(3200, 80)`
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", script)
_ = cmd.Run()
default:
cmd := exec.CommandContext(ctx, "say", "-v", "Fred", "Woof woof")
} else {
fmt.Print("\a")
}
}()
case "dragon":
go func() {
if runtime.GOOS == "windows" {
script := `[console]::beep(220, 100); [console]::beep(180, 150); [console]::beep(140, 200)`
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", script)
_ = cmd.Run()
} else {
fmt.Print("\a")
}
} else {
// Linux bell fallback
fmt.Print("\a")
}
}()
}()
default:
playWAV(dogBarkWAV)
}
}

// Bark is a backwards-compatible alias for the dog vocalization
// Bark is a backwards-compatible alias for the dog bark
func Bark() {
PlayCompanionSound("dog")
}
Expand Down
23 changes: 23 additions & 0 deletions internal/audio/audio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package audio

import (
"testing"
"time"
)

func TestPlayCompanionSound(t *testing.T) {
if len(dogBarkWAV) == 0 {
t.Fatal("expected embedded dogBarkWAV to be non-empty")
}
if len(catMeowWAV) == 0 {
t.Fatal("expected embedded catMeowWAV to be non-empty")
}

// Test calls without panic
PlayCompanionSound("dog")
PlayCompanionSound("cat")
PlayCompanionSound("dragon")
PlayCompanionSound("drone")
Bark()
time.Sleep(100 * time.Millisecond)
}
28 changes: 28 additions & 0 deletions internal/audio/play_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//go:build !windows

package audio

import (
"os"
"os/exec"
"runtime"
)

func playWAV(data []byte) {
if len(data) == 0 {
return
}
tmp, err := os.CreateTemp("", "kernel_sound_*.wav")
if err != nil {
return
}
defer os.Remove(tmp.Name())
_, _ = tmp.Write(data)
_ = tmp.Close()

if runtime.GOOS == "darwin" {
_ = exec.Command("afplay", tmp.Name()).Run()
} else {
_ = exec.Command("aplay", tmp.Name()).Run()
}
}
29 changes: 29 additions & 0 deletions internal/audio/play_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build windows

package audio

import (
"syscall"
"unsafe"
)

var (
winmm = syscall.NewLazyDLL("winmm.dll")
procPlaySoundW = winmm.NewProc("PlaySoundW")
)

const (
SND_ASYNC = 0x0001
SND_MEMORY = 0x0004
)

func playWAV(data []byte) {
if len(data) == 0 {
return
}
procPlaySoundW.Call(
uintptr(unsafe.Pointer(&data[0])),
0,
uintptr(SND_MEMORY|SND_ASYNC),
)
}
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Config struct {
CustomEndpoint string `json:"custom_endpoint,omitempty"`
CustomKey string `json:"custom_key,omitempty"`
CustomModel string `json:"custom_model,omitempty"`
Theme string `json:"theme,omitempty"`
}

func GetConfigDir() (string, error) {
Expand Down Expand Up @@ -82,6 +83,10 @@ func LoadConfig() (*Config, error) {
cfg.CustomEndpoint = envEndpoint
}

if cfg.Theme == "" {
cfg.Theme = "cyberpunk"
}

return &cfg, nil
}

Expand Down
112 changes: 112 additions & 0 deletions internal/tools/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package tools

import (
"context"
"fmt"
"strings"
"time"

"github.com/404kernel/kernel/internal/exec"
)

type GitChanges struct {
HasChanges bool
Status string
Diff string
Files []string
}

// InspectGitChanges queries git status and git diff in the current workspace.
func InspectGitChanges() GitChanges {
ctx := context.Background()

statusRes := exec.RunShellCommand(ctx, "git status --porcelain", 5*time.Second)
statusText := strings.TrimSpace(statusRes.Stdout)
if statusText == "" {
return GitChanges{HasChanges: false}
}

var files []string
for _, line := range strings.Split(statusText, "\n") {
trimmed := strings.TrimSpace(line)
if len(trimmed) > 3 {
files = append(files, strings.TrimSpace(trimmed[2:]))
}
}

// First try staged diff
diffRes := exec.RunShellCommand(ctx, "git diff --cached", 5*time.Second)
diffText := strings.TrimSpace(diffRes.Stdout)

// If no staged diff, get unstaged working diff
if diffText == "" {
diffRes = exec.RunShellCommand(ctx, "git diff", 5*time.Second)
diffText = strings.TrimSpace(diffRes.Stdout)
}

// Truncate diff if massive to prevent token explosion
if len(diffText) > 6000 {
diffText = diffText[:6000] + "\n... [diff truncated for length] ..."
}

return GitChanges{
HasChanges: true,
Status: statusText,
Diff: diffText,
Files: files,
}
}

// BuildCommitPrompt crafts an instruction prompt for the LLM to output a conventional commit.
func BuildCommitPrompt(changes GitChanges) string {
return fmt.Sprintf(`You are an expert Git and software release assistant.
Based on the following git status and git diff, write a concise, professional Conventional Commit message.

Rules:
1. First line must be formatted as: type(scope): short imperative summary (max 72 chars).
Types: feat, fix, docs, style, refactor, perf, test, chore.
2. If helpful, include 2-3 short bullet points after an empty line describing key changes.
3. Output ONLY the commit message. No explanations, no markdown code fences, no surrounding quotes.

Git Status:
%s

Git Diff:
%s
`, changes.Status, changes.Diff)
}

// HeuristicCommitMessage generates an intelligent fallback commit message offline or in demo mode.
func HeuristicCommitMessage(changes GitChanges) string {
if len(changes.Files) == 0 {
return "chore: update workspace files"
}

scope := "core"
firstFile := changes.Files[0]
if strings.Contains(firstFile, "ui/") || strings.Contains(firstFile, "styles") {
scope = "ui"
} else if strings.Contains(firstFile, "tools/") {
scope = "tools"
} else if strings.Contains(firstFile, "config/") {
scope = "config"
} else if strings.HasSuffix(firstFile, ".md") {
return "docs: update documentation and usage guides"
}

// Detect if features were added
hasAdditions := strings.Contains(changes.Diff, "+")
if hasAdditions {
return fmt.Sprintf("feat(%s): implement new functionality and enhancements across %d files", scope, len(changes.Files))
}

return fmt.Sprintf("refactor(%s): update and refine %s", scope, firstFile)
}

// RunGitCommit executes git add and git commit with the given message.
func RunGitCommit(message string) exec.ExecResult {
// Escape double quotes for shell execution
escaped := strings.ReplaceAll(message, `"`, `\"`)
cmd := fmt.Sprintf(`git add -A && git commit -m "%s"`, escaped)
return exec.RunShellCommand(context.Background(), cmd, 10*time.Second)
}
41 changes: 41 additions & 0 deletions internal/tools/git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tools

import (
"strings"
"testing"
)

func TestHeuristicCommitMessage(t *testing.T) {
changes := GitChanges{
HasChanges: true,
Files: []string{"internal/ui/styles.go", "internal/ui/model.go"},
Diff: "+func ApplyTheme()",
}

msg := HeuristicCommitMessage(changes)
if !strings.HasPrefix(msg, "feat(ui):") {
t.Errorf("expected feat(ui): prefix, got: %s", msg)
}

docChanges := GitChanges{
HasChanges: true,
Files: []string{"README.md"},
Diff: "+update docs",
}
docMsg := HeuristicCommitMessage(docChanges)
if !strings.HasPrefix(docMsg, "docs:") {
t.Errorf("expected docs: prefix, got: %s", docMsg)
}
}

func TestBuildCommitPrompt(t *testing.T) {
changes := GitChanges{
HasChanges: true,
Status: "M internal/ui/model.go",
Diff: "+new feature",
}
prompt := BuildCommitPrompt(changes)
if !strings.Contains(prompt, "Conventional Commit") {
t.Errorf("expected prompt to mention Conventional Commit")
}
}
Loading
Loading