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: 1 addition & 1 deletion cmd/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
var agentCmd = &cobra.Command{
Use: "agent",
Short: "Manage custom agent personas",
Long: "Create, list, and manage custom agent personas stored in ~/.hawk/agents/.",
Long: "Create, list, and manage custom agent personas stored in Hawk user state.",
}

var agentListCmd = &cobra.Command{
Expand Down
8 changes: 2 additions & 6 deletions cmd/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/GrayCodeAI/hawk/internal/hooks/audit"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -175,16 +176,11 @@ type SessionInfo struct {
}

func discoverSessions(days int, projectFilter string) ([]SessionInfo, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}

cutoff := time.Now().AddDate(0, 0, -days)
var sessions []SessionInfo

// Scan hawk sessions directory
hawkDir := filepath.Join(home, ".hawk", "sessions")
hawkDir := storage.SessionsDir()
entries, err := os.ReadDir(hawkDir)
if err != nil && !os.IsNotExist(err) {
return nil, err
Expand Down
5 changes: 2 additions & 3 deletions cmd/bg_sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"strings"
"time"

"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/spf13/cobra"
)

Expand All @@ -19,8 +19,7 @@ import (
// ─────────────────────────────────────────────────────────────────────────────

func bgSessionsDir() string {
home := home.Dir()
return filepath.Join(home, ".hawk", "bg-sessions")
return filepath.Join(storage.StateDir(), "bg-sessions")
}

// BGSessionInfo tracks a running background session.
Expand Down
13 changes: 5 additions & 8 deletions cmd/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import (
"github.com/GrayCodeAI/hawk/internal/engine"
"github.com/GrayCodeAI/hawk/internal/feature/shellmode"
"github.com/GrayCodeAI/hawk/internal/feature/taste"
"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/intelligence/memory"
"github.com/GrayCodeAI/hawk/internal/intelligence/repomap"
"github.com/GrayCodeAI/hawk/internal/observability/logger"
"github.com/GrayCodeAI/hawk/internal/plugin"
"github.com/GrayCodeAI/hawk/internal/sandbox"
"github.com/GrayCodeAI/hawk/internal/session"
"github.com/GrayCodeAI/hawk/internal/startup"
hawkstorage "github.com/GrayCodeAI/hawk/internal/storage"
"github.com/GrayCodeAI/hawk/internal/system/staleness"
"github.com/GrayCodeAI/hawk/internal/ui/icons"
)
Expand Down Expand Up @@ -152,11 +152,9 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting

// Initialize conversation DAG for branching support
startup.MarkPhase("newChatModel:dag")
if home, err := os.UserHomeDir(); err == nil {
dagPath := filepath.Join(home, ".hawk", "sessions", "convo.db")
if dag, err := storage.NewDAG(dagPath, sid); err == nil {
sess.SetConvoDAG(dag)
}
dagPath := filepath.Join(hawkstorage.SessionsDir(), "convo.db")
if dag, err := storage.NewDAG(dagPath, sid); err == nil {
sess.SetConvoDAG(dag)
}
startup.EndPhase("newChatModel:dag")

Expand Down Expand Up @@ -241,8 +239,7 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting
// Check for crash recovery
startup.MarkPhase("newChatModel:crash-recovery")
if recovered := session.CheckForRecovery(); len(recovered) > 0 {
home := home.Dir()
walDir := filepath.Join(home, ".hawk", "sessions")
walDir := hawkstorage.SessionsDir()
for _, rid := range recovered {
if rid == sid {
continue // current session WAL
Expand Down
14 changes: 5 additions & 9 deletions cmd/chat_commands_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

tea "github.com/charmbracelet/bubbletea"

"github.com/GrayCodeAI/eyrie/client"

Check failure on line 14 in cmd/chat_commands_session.go

View workflow job for this annotation

GitHub Actions / deadcode

github.com/GrayCodeAI/eyrie@v0.1.0 (replaced by ./external/eyrie): reading external/eyrie/go.mod: open /home/runner/work/hawk/hawk/external/eyrie/go.mod: no such file or directory
"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/session"
"github.com/GrayCodeAI/hawk/internal/storage"
)

// saveSession persists the current session to disk.
Expand Down Expand Up @@ -234,8 +234,7 @@
return m, nil

case "/export":
homeDir := home.Dir()
exportDir := filepath.Join(homeDir, ".hawk", "exports")
exportDir := filepath.Join(storage.StateDir(), "exports")
_ = os.MkdirAll(exportDir, 0o755)
exportPath := filepath.Join(exportDir, m.sessionID+".md")
var md strings.Builder
Expand All @@ -258,8 +257,7 @@
return m, nil

case "/share":
homeDir := home.Dir()
exportDir := filepath.Join(homeDir, ".hawk", "exports")
exportDir := filepath.Join(storage.StateDir(), "exports")
_ = os.MkdirAll(exportDir, 0o755)
exportPath := filepath.Join(exportDir, m.sessionID+".md")
var md strings.Builder
Expand All @@ -286,8 +284,7 @@
return m, nil
}
newName := parts[1]
homeDir := home.Dir()
sessDir := filepath.Join(homeDir, ".hawk", "sessions")
sessDir := storage.SessionsDir()
oldPath := filepath.Join(sessDir, m.sessionID+".jsonl")
newPath := filepath.Join(sessDir, newName+".jsonl")
if err := os.Rename(oldPath, newPath); err != nil {
Expand All @@ -303,8 +300,7 @@
m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /tag <label>"})
return m, nil
}
homeDir := home.Dir()
tagFile := filepath.Join(homeDir, ".hawk", "sessions", m.sessionID+".tags")
tagFile := filepath.Join(storage.SessionsDir(), m.sessionID+".tags")
f, err := os.OpenFile(tagFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()})
Expand Down
2 changes: 1 addition & 1 deletion cmd/chat_commands_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func sessionStats(sess *engine.Session, id string) string {
}

func hooksSummary() string {
return "Hooks: pre_query, post_query, pre_tool, post_tool, session_start, session_end, permission_ask, error\nConfigure in .hawk/settings.json or ~/.hawk/settings.json"
return "Hooks: pre_query, post_query, pre_tool, post_tool, session_start, session_end, permission_ask, error\nConfigure in Hawk user settings"
}

func pluginsSummary(rt *plugin.Runtime) string {
Expand Down
4 changes: 2 additions & 2 deletions cmd/chat_focus.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
tea "github.com/charmbracelet/bubbletea"

"github.com/GrayCodeAI/hawk/internal/engine"
"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/GrayCodeAI/hawk/internal/ui/icons"
)

Expand All @@ -25,7 +25,7 @@ const (
const toolDisplayMaxChars = 12000 // preview cap in scrollback (Grok uses ~20k for bash)

func sessionExportPath(sessionID string) string {
return filepath.Join(home.Dir(), ".hawk", "exports", sessionID+".md")
return filepath.Join(storage.StateDir(), "exports", sessionID+".md")
}

func (m *chatModel) inScrollbackFocus() bool {
Expand Down
6 changes: 3 additions & 3 deletions cmd/chat_subcommand_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
)

// memorySubcommand implements the /memory slash command. It
// prints the project's AGENTS.md (or .hawk/AGENTS.md) content.
// memorySubcommand implements the /memory slash command.
// It prints the project's AGENTS.md content.
type memorySubcommand struct{}

func (m *memorySubcommand) Name() string { return "memory" }
Expand All @@ -19,7 +19,7 @@ func (m *memorySubcommand) Usage() string { return "" }
func (m *memorySubcommand) Handle(ml *chatModel, args []string, text string) (tea.Model, tea.Cmd) {
md := strings.TrimSpace(hawkconfig.LoadAgentsMD())
if md == "" {
ml.messages = append(ml.messages, displayMsg{role: "system", content: "No AGENTS.md or .hawk/AGENTS.md project instructions found.\nUse /yaad for persistent graph memory."})
ml.messages = append(ml.messages, displayMsg{role: "system", content: "No AGENTS.md project instructions found.\nUse /yaad for persistent graph memory."})
} else {
ml.messages = append(ml.messages, displayMsg{role: "system", content: "Project instructions (AGENTS.md):\n" + md})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/chat_subcommand_recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (r *recipeSubcommand) Handle(m *chatModel, args []string, text string) (tea
rn := recipe.NewRunner()
recipes := rn.List()
if len(recipes) == 0 {
m.messages = append(m.messages, displayMsg{role: "system", content: "No recipes found in ~/.hawk/recipes/ or .hawk/recipes/"})
m.messages = append(m.messages, displayMsg{role: "system", content: "No recipes found in Hawk user state or .agents/recipes/"})
} else {
var list string
for _, r := range recipes {
Expand Down
11 changes: 5 additions & 6 deletions cmd/chat_subcommand_simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
tea "github.com/charmbracelet/bubbletea"

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
"github.com/GrayCodeAI/hawk/internal/home"
analytics "github.com/GrayCodeAI/hawk/internal/observability"
"github.com/GrayCodeAI/hawk/internal/plugin"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/GrayCodeAI/hawk/internal/tool"
)

Expand Down Expand Up @@ -645,19 +645,18 @@ func init() {
},
})

// /feedback <msg> — submit feedback saved to ~/.hawk/feedback/
// /feedback <msg> — submit feedback saved to Hawk user state.
subcommandRegistry.Register(&delegatingCommand{
name: "feedback",
description: "submit feedback (saved to ~/.hawk/feedback/)",
description: "submit feedback (saved to Hawk user state)",
usage: "/feedback <message>",
handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) {
body := strings.TrimSpace(strings.TrimPrefix(text, "/feedback"))
if body == "" {
m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /feedback <message>\nCaptures session context and saves feedback to ~/.hawk/feedback/"})
m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /feedback <message>\nCaptures session context and saves feedback to Hawk user state."})
return m, nil
}
homeDir := home.Dir()
feedDir := filepath.Join(homeDir, ".hawk", "feedback")
feedDir := filepath.Join(storage.StateDir(), "feedback")
_ = os.MkdirAll(feedDir, 0o755)
report := fmt.Sprintf(`{"timestamp":%q,"version":%q,"model":%q,"provider":%q,"category":"session","body":%q,"session_id":%q}`,
time.Now().Format(time.RFC3339), version, m.session.Model(), m.session.Provider(), body, m.sessionID)
Expand Down
16 changes: 0 additions & 16 deletions cmd/chat_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package cmd

import (
"context"
"fmt"
"os"
"strings"

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
Expand Down Expand Up @@ -85,24 +83,10 @@ func defaultRegistry(settings hawkconfig.Settings) (*tool.Registry, error) {
if tool.IsPowerShellAvailable() {
tools = append(tools, tool.PowerShellTool{})
}
// Detect project-level MCP servers (supply chain attack vector).
// Project .hawk/settings.json can be committed to a repo and define
// arbitrary commands that execute on clone. Gate behind --allow-project-mcp.
projectMCPServers := hawkconfig.ProjectMCPServers()
projectMCPNames := make(map[string]bool, len(projectMCPServers))
for _, cfg := range projectMCPServers {
if cfg.Name != "" {
projectMCPNames[cfg.Name] = true
}
}
for _, cfg := range settings.MCPServers {
if cfg.Name == "" || cfg.Command == "" {
continue
}
if projectMCPNames[cfg.Name] && !allowProjectMCP {
fmt.Fprintf(os.Stderr, "hawk: skipping project-level MCP server %q (defined in .hawk/settings.json); use --allow-project-mcp to enable\n", cfg.Name)
continue
}
mcpTools, err := tool.LoadMCPTools(context.Background(), cfg.Name, cfg.Command, cfg.Args...)
if err != nil {
continue
Expand Down
7 changes: 2 additions & 5 deletions cmd/cmdhistory_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"

"github.com/GrayCodeAI/hawk/internal/cmdhistory"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -158,11 +159,7 @@ func init() {
}

func openCmdHistoryStore() (*cmdhistory.Store, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("cannot determine home directory: %w", err)
}
dbPath := filepath.Join(home, ".hawk", "cmd-history.db")
dbPath := filepath.Join(storage.StateDir(), "cmd-history.db")

// Ensure the directory exists.
if mkErr := os.MkdirAll(filepath.Dir(dbPath), 0o755); mkErr != nil {
Expand Down
10 changes: 4 additions & 6 deletions cmd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (
hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
"github.com/GrayCodeAI/hawk/internal/daemon"
"github.com/GrayCodeAI/hawk/internal/engine"
"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/netutil"
"github.com/GrayCodeAI/hawk/internal/observability/logger"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -130,7 +130,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error {
} else {
fmt.Println("API key: (set via --api-key or HAWK_DAEMON_API_KEY)")
}
keyFile := filepath.Join(home.Dir(), ".hawk", "run", "daemon.key")
keyFile := filepath.Join(storage.DaemonRunDir(), "daemon.key")
_ = os.MkdirAll(filepath.Dir(keyFile), 0o700)
if err := os.WriteFile(keyFile, []byte(apiKey), 0o600); err == nil {
fmt.Printf("Full API key written to %s\n", keyFile)
Expand Down Expand Up @@ -202,8 +202,7 @@ func generateDaemonAPIKey() (string, error) {
}

func runDaemonStop(_ *cobra.Command, _ []string) error {
home := home.Dir()
pidFile := filepath.Join(home, ".hawk", "run", "daemon.json")
pidFile := filepath.Join(storage.DaemonRunDir(), "daemon.json")

data, err := os.ReadFile(pidFile)
if err != nil {
Expand Down Expand Up @@ -233,8 +232,7 @@ func runDaemonStop(_ *cobra.Command, _ []string) error {
}

func runDaemonStatus(_ *cobra.Command, _ []string) error {
home := home.Dir()
pidFile := filepath.Join(home, ".hawk", "run", "daemon.json")
pidFile := filepath.Join(storage.DaemonRunDir(), "daemon.json")

data, err := os.ReadFile(pidFile)
if err != nil {
Expand Down
10 changes: 2 additions & 8 deletions cmd/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/GrayCodeAI/hawk/internal/plugin"
"github.com/GrayCodeAI/hawk/internal/resilience/health"
"github.com/GrayCodeAI/hawk/internal/session"
"github.com/GrayCodeAI/hawk/internal/storage"
"github.com/GrayCodeAI/hawk/internal/ui/icons"
)

Expand Down Expand Up @@ -151,20 +152,13 @@ func healthCheckReport(settings hawkconfig.Settings, provider string) string {
// Config syntax check
registry.Register("config_syntax", func(ctx context.Context) health.Check {
start := time.Now()
home, _ := os.UserHomeDir()
globalPath := filepath.Join(home, ".hawk", "settings.json")
globalPath := storage.SettingsPath()
if data, err := os.ReadFile(globalPath); err == nil {
var raw json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return health.Check{Name: "config_syntax", Status: health.Unhealthy, Message: fmt.Sprintf("global settings.json parse error: %v", err), Duration: time.Since(start)}
}
}
if data, err := os.ReadFile(".hawk/settings.json"); err == nil {
var raw json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return health.Check{Name: "config_syntax", Status: health.Unhealthy, Message: fmt.Sprintf("project settings.json parse error: %v", err), Duration: time.Since(start)}
}
}
return health.Check{Name: "config_syntax", Status: health.Healthy, Message: "config files parse OK", Duration: time.Since(start)}
})

Expand Down
5 changes: 2 additions & 3 deletions cmd/dx.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
"github.com/GrayCodeAI/hawk/internal/engine"
"github.com/GrayCodeAI/hawk/internal/home"
"github.com/GrayCodeAI/hawk/internal/plugin"
"github.com/GrayCodeAI/hawk/internal/storage"
)

// startTime records when the process started, used by debugOutput for uptime.
Expand Down Expand Up @@ -78,8 +78,7 @@ func doctorOutput(settings hawkconfig.Settings) string {

// Session directory status
b.WriteString("\nSession directory:\n")
home := home.Dir()
sessDir := filepath.Join(home, ".hawk", "sessions")
sessDir := storage.SessionsDir()
if info, err := os.Stat(sessDir); err != nil {
b.WriteString(fmt.Sprintf(" Status: missing (%s)\n", sessDir))
} else if !info.IsDir() {
Expand Down
Loading
Loading