From 0b3e5f5bd42a02c2a15b394b3768e517dc43f39c Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Mon, 14 Apr 2025 11:24:36 +0200 Subject: handle errors correctly in the other tools --- internal/llm/tools/fetch.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'internal/llm/tools/fetch.go') diff --git a/internal/llm/tools/fetch.go b/internal/llm/tools/fetch.go index 19e644281..91bcb36a0 100644 --- a/internal/llm/tools/fetch.go +++ b/internal/llm/tools/fetch.go @@ -86,6 +86,7 @@ func (t *fetchTool) Info() ToolInfo { "format": map[string]any{ "type": "string", "description": "The format to return the content in (text, markdown, or html)", + "enum": []string{"text", "markdown", "html"}, }, "timeout": map[string]any{ "type": "number", @@ -126,7 +127,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error ) if !p { - return NewTextErrorResponse("Permission denied to fetch from URL: " + params.URL), nil + return ToolResponse{}, permission.ErrorPermissionDenied } client := t.client @@ -142,14 +143,14 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error req, err := http.NewRequestWithContext(ctx, "GET", params.URL, nil) if err != nil { - return NewTextErrorResponse("Failed to create request: " + err.Error()), nil + return ToolResponse{}, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("User-Agent", "termai/1.0") resp, err := client.Do(req) if err != nil { - return NewTextErrorResponse("Failed to execute request: " + err.Error()), nil + return ToolResponse{}, fmt.Errorf("failed to fetch URL: %w", err) } defer resp.Body.Close() -- cgit v1.2.3 From cc07f7a186995f428436bc1adc66a264a95171a4 Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Wed, 16 Apr 2025 21:48:29 +0200 Subject: rename to opencode --- .opencode.json | 11 ++++ cmd/root.go | 14 ++-- go.mod | 2 +- internal/app/app.go | 18 +++--- internal/app/lsp.go | 8 +-- internal/config/config.go | 4 +- internal/db/connect.go | 6 +- internal/diff/diff.go | 4 +- internal/history/file.go | 4 +- internal/llm/agent/agent-tool.go | 10 +-- internal/llm/agent/agent.go | 18 +++--- internal/llm/agent/mcp-tools.go | 10 +-- internal/llm/agent/tools.go | 12 ++-- internal/llm/prompt/coder.go | 97 ++++++++++++---------------- internal/llm/prompt/prompt.go | 4 +- internal/llm/prompt/task.go | 4 +- internal/llm/prompt/title.go | 2 +- internal/llm/provider/anthropic.go | 8 +-- internal/llm/provider/bedrock.go | 4 +- internal/llm/provider/gemini.go | 8 +-- internal/llm/provider/openai.go | 8 +-- internal/llm/provider/provider.go | 6 +- internal/llm/tools/bash.go | 16 ++--- internal/llm/tools/diagnostics.go | 4 +- internal/llm/tools/edit.go | 10 +-- internal/llm/tools/edit_test.go | 2 +- internal/llm/tools/fetch.go | 6 +- internal/llm/tools/glob.go | 2 +- internal/llm/tools/grep.go | 2 +- internal/llm/tools/ls.go | 2 +- internal/llm/tools/mocks_test.go | 6 +- internal/llm/tools/shell/shell.go | 8 +-- internal/llm/tools/sourcegraph.go | 2 +- internal/llm/tools/view.go | 4 +- internal/llm/tools/write.go | 10 +-- internal/llm/tools/write_test.go | 2 +- internal/logging/writer.go | 2 +- internal/lsp/client.go | 6 +- internal/lsp/handlers.go | 8 +-- internal/lsp/language.go | 2 +- internal/lsp/methods.go | 2 +- internal/lsp/transport.go | 4 +- internal/lsp/util/edit.go | 2 +- internal/lsp/watcher/watcher.go | 8 +-- internal/message/content.go | 2 +- internal/message/message.go | 6 +- internal/permission/permission.go | 2 +- internal/session/session.go | 4 +- internal/tui/components/chat/chat.go | 8 +-- internal/tui/components/chat/editor.go | 10 +-- internal/tui/components/chat/messages.go | 22 +++---- internal/tui/components/chat/sidebar.go | 12 ++-- internal/tui/components/core/status.go | 12 ++-- internal/tui/components/dialog/help.go | 2 +- internal/tui/components/dialog/permission.go | 12 ++-- internal/tui/components/dialog/quit.go | 6 +- internal/tui/components/logs/details.go | 6 +- internal/tui/components/logs/table.go | 10 +-- internal/tui/layout/border.go | 2 +- internal/tui/layout/container.go | 2 +- internal/tui/layout/overlay.go | 4 +- internal/tui/layout/split.go | 2 +- internal/tui/page/chat.go | 10 +-- internal/tui/page/logs.go | 4 +- internal/tui/tui.go | 18 +++--- main.go | 4 +- 66 files changed, 266 insertions(+), 266 deletions(-) (limited to 'internal/llm/tools/fetch.go') diff --git a/.opencode.json b/.opencode.json index b7fc19b52..4b2944f86 100644 --- a/.opencode.json +++ b/.opencode.json @@ -3,5 +3,16 @@ "gopls": { "command": "gopls" } + }, + "agents": { + "coder": { + "model": "gpt-4.1" + }, + "task": { + "model": "gpt-4.1" + }, + "title": { + "model": "gpt-4.1" + } } } diff --git a/cmd/root.go b/cmd/root.go index ff71747d5..f506e9940 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,13 +8,13 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" - "github.com/kujtimiihoxha/termai/internal/app" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/db" - "github.com/kujtimiihoxha/termai/internal/llm/agent" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/pubsub" - "github.com/kujtimiihoxha/termai/internal/tui" + "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/db" + "github.com/kujtimiihoxha/opencode/internal/llm/agent" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/tui" zone "github.com/lrstanley/bubblezone" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index 16c88d3a6..822e70dbd 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/kujtimiihoxha/termai +module github.com/kujtimiihoxha/opencode go 1.24.0 diff --git a/internal/app/app.go b/internal/app/app.go index 1c16ccc11..748fdaa7f 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -7,15 +7,15 @@ import ( "sync" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/db" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/llm/agent" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/message" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/session" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/db" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/llm/agent" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/message" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/session" ) type App struct { diff --git a/internal/app/lsp.go b/internal/app/lsp.go index 4a762f1a1..d8a35c8b3 100644 --- a/internal/app/lsp.go +++ b/internal/app/lsp.go @@ -4,10 +4,10 @@ import ( "context" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/lsp/watcher" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp/watcher" ) func (app *App) initLSPClients(ctx context.Context) { diff --git a/internal/config/config.go b/internal/config/config.go index 147d6c83a..20a8bac97 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/logging" "github.com/spf13/viper" ) diff --git a/internal/db/connect.go b/internal/db/connect.go index 8bba9cad8..e850bc8d0 100644 --- a/internal/db/connect.go +++ b/internal/db/connect.go @@ -12,8 +12,8 @@ import ( "github.com/golang-migrate/migrate/v4/database/sqlite3" _ "github.com/mattn/go-sqlite3" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" ) func Connect() (*sql.DB, error) { @@ -24,7 +24,7 @@ func Connect() (*sql.DB, error) { if err := os.MkdirAll(dataDir, 0o700); err != nil { return nil, fmt.Errorf("failed to create data directory: %w", err) } - dbPath := filepath.Join(dataDir, "termai.db") + dbPath := filepath.Join(dataDir, "opencode.db") // Open the SQLite database db, err := sql.Open("sqlite3", dbPath) if err != nil { diff --git a/internal/diff/diff.go b/internal/diff/diff.go index 829554c7e..f48079c9c 100644 --- a/internal/diff/diff.go +++ b/internal/diff/diff.go @@ -19,8 +19,8 @@ import ( "github.com/charmbracelet/x/ansi" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" "github.com/sergi/go-diff/diffmatchpatch" ) diff --git a/internal/history/file.go b/internal/history/file.go index 82017d4cf..1e8bc50bb 100644 --- a/internal/history/file.go +++ b/internal/history/file.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/db" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/db" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) const ( diff --git a/internal/llm/agent/agent-tool.go b/internal/llm/agent/agent-tool.go index 308412bde..be6e09a9b 100644 --- a/internal/llm/agent/agent-tool.go +++ b/internal/llm/agent/agent-tool.go @@ -5,11 +5,11 @@ import ( "encoding/json" "fmt" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/message" - "github.com/kujtimiihoxha/termai/internal/session" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/message" + "github.com/kujtimiihoxha/opencode/internal/session" ) type agentTool struct { diff --git a/internal/llm/agent/agent.go b/internal/llm/agent/agent.go index ab2742ec1..a5dadb89d 100644 --- a/internal/llm/agent/agent.go +++ b/internal/llm/agent/agent.go @@ -7,15 +7,15 @@ import ( "strings" "sync" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/llm/prompt" - "github.com/kujtimiihoxha/termai/internal/llm/provider" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/message" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/session" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/prompt" + "github.com/kujtimiihoxha/opencode/internal/llm/provider" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/message" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/session" ) // Common errors diff --git a/internal/llm/agent/mcp-tools.go b/internal/llm/agent/mcp-tools.go index c7ea4916c..16dddc1ba 100644 --- a/internal/llm/agent/mcp-tools.go +++ b/internal/llm/agent/mcp-tools.go @@ -5,11 +5,11 @@ import ( "encoding/json" "fmt" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/version" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/version" "github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/mcp" diff --git a/internal/llm/agent/tools.go b/internal/llm/agent/tools.go index a37f1d65d..409d14273 100644 --- a/internal/llm/agent/tools.go +++ b/internal/llm/agent/tools.go @@ -3,12 +3,12 @@ package agent import ( "context" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/message" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/session" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/message" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/session" ) func CoderAgentTools( diff --git a/internal/llm/prompt/coder.go b/internal/llm/prompt/coder.go index 7439fd570..3a06911da 100644 --- a/internal/llm/prompt/coder.go +++ b/internal/llm/prompt/coder.go @@ -8,9 +8,9 @@ import ( "runtime" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" ) func CoderPrompt(provider models.ModelProvider) string { @@ -24,69 +24,58 @@ func CoderPrompt(provider models.ModelProvider) string { return fmt.Sprintf("%s\n\n%s\n%s", basePrompt, envInfo, lspInformation()) } -const baseOpenAICoderPrompt = `You are termAI, an autonomous CLI-based software engineer. Your job is to reduce user effort by proactively reasoning, inferring context, and solving software engineering tasks end-to-end with minimal prompting. - -# Your mindset -Act like a competent, efficient software engineer who is familiar with large codebases. You should: -- Think critically about user requests. -- Proactively search the codebase for related information. -- Infer likely commands, tools, or conventions. -- Write and edit code with minimal user input. -- Anticipate next steps (tests, lints, etc.), but never commit unless explicitly told. - -# Context awareness -- Before acting, infer the purpose of a file from its name, directory, and neighboring files. -- If a file or function appears malicious, refuse to interact with it or discuss it. -- If a termai.md file exists, auto-load it as memory. Offer to update it only if new useful info appears (commands, preferences, structure). - -# CLI communication -- Use GitHub-flavored markdown in monospace font. -- Be concise. Never add preambles or postambles unless asked. Max 4 lines per response. -- Never explain your code unless asked. Do not narrate actions. -- Avoid unnecessary questions. Infer, search, act. - -# Behavior guidelines -- Follow project conventions: naming, formatting, libraries, frameworks. -- Before using any library or framework, confirm it’s already used. -- Always look at the surrounding code to match existing style. -- Do not add comments unless the code is complex or the user asks. - -# Autonomy rules -You are allowed and expected to: -- Search for commands, tools, or config files before asking the user. -- Run multiple search tool calls concurrently to gather relevant context. -- Choose test, lint, and typecheck commands based on package files or scripts. -- Offer to store these commands in termai.md if not already present. - -# Example behavior -user: write tests for new feature -assistant: [searches for existing test patterns, finds appropriate location, generates test code using existing style, optionally asks to add test command to termai.md] +const baseOpenAICoderPrompt = ` +You are **OpenCode**, an autonomous CLI assistant for software‑engineering tasks. + +### ── INTERNAL REFLECTION ── +• Silently think step‑by‑step about the user request, directory layout, and tool calls (never reveal this). +• Formulate a plan, then execute without further approval unless a blocker triggers the Ask‑Only‑If rules. + +### ── PUBLIC RESPONSE RULES ── +• Visible reply ≤ 4 lines; no fluff, preamble, or postamble. +• Use GitHub‑flavored Markdown. +• When running a non‑trivial shell command, add ≤ 1 brief purpose sentence. + +### ── CONTEXT & MEMORY ── +• Infer file intent from directory structure before editing. +• Auto‑load 'OpenCode.md'; ask once before writing new reusable commands or style notes. -user: how do I typecheck this codebase? -assistant: [searches for known commands, infers package manager, checks for scripts or config files] -tsc --noEmit +### ── AUTONOMY PRIORITY ── +**Ask‑Only‑If Decision Tree:** +1. **Safety risk?** (e.g., destructive command, secret exposure) → ask. +2. **Critical unknown?** (no docs/tests; cannot infer) → ask. +3. **Tool failure after two self‑attempts?** → ask. +Otherwise, proceed autonomously. -user: is X function used anywhere else? -assistant: [searches repo for references, returns file paths and lines] +### ── SAFETY & STYLE ── +• Mimic existing code style; verify libraries exist before import. +• Never commit unless explicitly told. +• After edits, run lint & type‑check (ask for commands once, then offer to store in 'OpenCode.md'). +• Protect secrets; follow standard security practices :contentReference[oaicite:2]{index=2}. -# Tool usage -- Use parallel calls when possible. -- Use file search and content tools before asking the user. -- Do not ask the user for information unless it cannot be determined via tools. +### ── TOOL USAGE ── +• Batch independent Agent search/file calls in one block for efficiency :contentReference[oaicite:3]{index=3}. +• Communicate with the user only via visible text; do not expose tool output or internal reasoning. -Never commit changes unless the user explicitly asks you to.` +### ── EXAMPLES ── +user: list files +assistant: ls + +user: write tests for new feature +assistant: [searches & edits autonomously, no extra chit‑chat] +` -const baseAnthropicCoderPrompt = `You are termAI, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. +const baseAnthropicCoderPrompt = `You are OpenCode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. # Memory -If the current working directory contains a file called termai.md, it will be automatically added to your context. This file serves multiple purposes: +If the current working directory contains a file called OpenCode.md, it will be automatically added to your context. This file serves multiple purposes: 1. Storing frequently used bash commands (build, test, lint, etc.) so you can use them without searching each time 2. Recording the user's code style preferences (naming conventions, preferred libraries, etc.) 3. Maintaining useful information about the codebase structure and organization -When you spend time searching for commands to typecheck, lint, build, or test, you should ask the user if it's okay to add those commands to termai.md. Similarly, when learning about code style preferences or important codebase information, ask if it's okay to add that to termai.md so you can remember it for next time. +When you spend time searching for commands to typecheck, lint, build, or test, you should ask the user if it's okay to add those commands to OpenCode.md. Similarly, when learning about code style preferences or important codebase information, ask if it's okay to add that to OpenCode.md so you can remember it for next time. # Tone and style You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). @@ -161,7 +150,7 @@ The user will primarily request you perform software engineering tasks. This inc 1. Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially. 2. Implement the solution using all tools available to you 3. Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach. -4. VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (eg. npm run lint, npm run typecheck, ruff, etc.) if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to termai.md so that you will know to run it next time. +4. VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (eg. npm run lint, npm run typecheck, ruff, etc.) if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to opencode.md so that you will know to run it next time. NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive. diff --git a/internal/llm/prompt/prompt.go b/internal/llm/prompt/prompt.go index 63fc2df7b..cdc3560ce 100644 --- a/internal/llm/prompt/prompt.go +++ b/internal/llm/prompt/prompt.go @@ -1,8 +1,8 @@ package prompt import ( - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/models" ) func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string { diff --git a/internal/llm/prompt/task.go b/internal/llm/prompt/task.go index 8bf604ad9..88cd1a0f4 100644 --- a/internal/llm/prompt/task.go +++ b/internal/llm/prompt/task.go @@ -3,11 +3,11 @@ package prompt import ( "fmt" - "github.com/kujtimiihoxha/termai/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/models" ) func TaskPrompt(_ models.ModelProvider) string { - agentPrompt := `You are an agent for termAI. Given the user's prompt, you should use the tools available to you to answer the user's question. + agentPrompt := `You are an agent for OpenCode. Given the user's prompt, you should use the tools available to you to answer the user's question. Notes: 1. IMPORTANT: You should be concise, direct, and to the point, since your responses will be displayed on a command line interface. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is .", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". 2. When relevant, share file names and code snippets relevant to the query diff --git a/internal/llm/prompt/title.go b/internal/llm/prompt/title.go index 3023a8550..6e5289b24 100644 --- a/internal/llm/prompt/title.go +++ b/internal/llm/prompt/title.go @@ -1,6 +1,6 @@ package prompt -import "github.com/kujtimiihoxha/termai/internal/llm/models" +import "github.com/kujtimiihoxha/opencode/internal/llm/models" func TitlePrompt(_ models.ModelProvider) string { return `you will generate a short title based on the first message a user begins a conversation with diff --git a/internal/llm/provider/anthropic.go b/internal/llm/provider/anthropic.go index c3a4efc49..7bbc02103 100644 --- a/internal/llm/provider/anthropic.go +++ b/internal/llm/provider/anthropic.go @@ -12,10 +12,10 @@ import ( "github.com/anthropics/anthropic-sdk-go" "github.com/anthropics/anthropic-sdk-go/bedrock" "github.com/anthropics/anthropic-sdk-go/option" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/message" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/message" ) type anthropicOptions struct { diff --git a/internal/llm/provider/bedrock.go b/internal/llm/provider/bedrock.go index d76925ad1..9415b30fe 100644 --- a/internal/llm/provider/bedrock.go +++ b/internal/llm/provider/bedrock.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/message" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/message" ) type bedrockOptions struct { diff --git a/internal/llm/provider/gemini.go b/internal/llm/provider/gemini.go index 804baea28..384bff900 100644 --- a/internal/llm/provider/gemini.go +++ b/internal/llm/provider/gemini.go @@ -11,10 +11,10 @@ import ( "github.com/google/generative-ai-go/genai" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/message" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/message" "google.golang.org/api/iterator" "google.golang.org/api/option" ) diff --git a/internal/llm/provider/openai.go b/internal/llm/provider/openai.go index 9c2ad2012..13ce934f2 100644 --- a/internal/llm/provider/openai.go +++ b/internal/llm/provider/openai.go @@ -8,10 +8,10 @@ import ( "io" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/message" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/message" "github.com/openai/openai-go" "github.com/openai/openai-go/option" ) diff --git a/internal/llm/provider/provider.go b/internal/llm/provider/provider.go index 1a5b3dc8a..e04bee71b 100644 --- a/internal/llm/provider/provider.go +++ b/internal/llm/provider/provider.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/message" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/message" ) type EventType string diff --git a/internal/llm/tools/bash.go b/internal/llm/tools/bash.go index c7c970e5a..18533b761 100644 --- a/internal/llm/tools/bash.go +++ b/internal/llm/tools/bash.go @@ -7,9 +7,9 @@ import ( "strings" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/tools/shell" - "github.com/kujtimiihoxha/termai/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/tools/shell" + "github.com/kujtimiihoxha/opencode/internal/permission" ) type BashParams struct { @@ -122,16 +122,16 @@ When the user asks you to create a new git commit, follow these steps carefully: 4. Create the commit with a message ending with: -🤖 Generated with termai -Co-Authored-By: termai +🤖 Generated with opencode +Co-Authored-By: opencode - In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example: git commit -m "$(cat <<'EOF' Commit message here. - 🤖 Generated with termai - Co-Authored-By: termai + 🤖 Generated with opencode + Co-Authored-By: opencode EOF )" @@ -193,7 +193,7 @@ gh pr create --title "the pr title" --body "$(cat <<'EOF' ## Test plan [Checklist of TODOs for testing the pull request...] -🤖 Generated with termai +🤖 Generated with opencode EOF )" diff --git a/internal/llm/tools/diagnostics.go b/internal/llm/tools/diagnostics.go index b7b2bb8ba..82989c774 100644 --- a/internal/llm/tools/diagnostics.go +++ b/internal/llm/tools/diagnostics.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) type DiagnosticsParams struct { diff --git a/internal/llm/tools/edit.go b/internal/llm/tools/edit.go index 148e7aba7..6a1616010 100644 --- a/internal/llm/tools/edit.go +++ b/internal/llm/tools/edit.go @@ -9,11 +9,11 @@ import ( "strings" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/diff" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/diff" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/permission" ) type EditParams struct { diff --git a/internal/llm/tools/edit_test.go b/internal/llm/tools/edit_test.go index 0971775dd..1b58a0d7d 100644 --- a/internal/llm/tools/edit_test.go +++ b/internal/llm/tools/edit_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/kujtimiihoxha/termai/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/internal/llm/tools/fetch.go b/internal/llm/tools/fetch.go index 91bcb36a0..827755863 100644 --- a/internal/llm/tools/fetch.go +++ b/internal/llm/tools/fetch.go @@ -11,8 +11,8 @@ import ( md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/permission" ) type FetchParams struct { @@ -146,7 +146,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error return ToolResponse{}, fmt.Errorf("failed to create request: %w", err) } - req.Header.Set("User-Agent", "termai/1.0") + req.Header.Set("User-Agent", "opencode/1.0") resp, err := client.Do(req) if err != nil { diff --git a/internal/llm/tools/glob.go b/internal/llm/tools/glob.go index 7b4fb1187..40262ce2b 100644 --- a/internal/llm/tools/glob.go +++ b/internal/llm/tools/glob.go @@ -12,7 +12,7 @@ import ( "time" "github.com/bmatcuk/doublestar/v4" - "github.com/kujtimiihoxha/termai/internal/config" + "github.com/kujtimiihoxha/opencode/internal/config" ) const ( diff --git a/internal/llm/tools/grep.go b/internal/llm/tools/grep.go index 19333f50b..3436dd7eb 100644 --- a/internal/llm/tools/grep.go +++ b/internal/llm/tools/grep.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/kujtimiihoxha/termai/internal/config" + "github.com/kujtimiihoxha/opencode/internal/config" ) type GrepParams struct { diff --git a/internal/llm/tools/ls.go b/internal/llm/tools/ls.go index a63bf0eeb..05f300c0e 100644 --- a/internal/llm/tools/ls.go +++ b/internal/llm/tools/ls.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/kujtimiihoxha/termai/internal/config" + "github.com/kujtimiihoxha/opencode/internal/config" ) type LSParams struct { diff --git a/internal/llm/tools/mocks_test.go b/internal/llm/tools/mocks_test.go index 321f09ac1..81993160c 100644 --- a/internal/llm/tools/mocks_test.go +++ b/internal/llm/tools/mocks_test.go @@ -9,9 +9,9 @@ import ( "time" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) // Mock permission service for testing diff --git a/internal/llm/tools/shell/shell.go b/internal/llm/tools/shell/shell.go index 4a776478a..e25bdf3ea 100644 --- a/internal/llm/tools/shell/shell.go +++ b/internal/llm/tools/shell/shell.go @@ -126,10 +126,10 @@ func (s *PersistentShell) execCommand(command string, timeout time.Duration, ctx } tempDir := os.TempDir() - stdoutFile := filepath.Join(tempDir, fmt.Sprintf("termai-stdout-%d", time.Now().UnixNano())) - stderrFile := filepath.Join(tempDir, fmt.Sprintf("termai-stderr-%d", time.Now().UnixNano())) - statusFile := filepath.Join(tempDir, fmt.Sprintf("termai-status-%d", time.Now().UnixNano())) - cwdFile := filepath.Join(tempDir, fmt.Sprintf("termai-cwd-%d", time.Now().UnixNano())) + stdoutFile := filepath.Join(tempDir, fmt.Sprintf("opencode-stdout-%d", time.Now().UnixNano())) + stderrFile := filepath.Join(tempDir, fmt.Sprintf("opencode-stderr-%d", time.Now().UnixNano())) + statusFile := filepath.Join(tempDir, fmt.Sprintf("opencode-status-%d", time.Now().UnixNano())) + cwdFile := filepath.Join(tempDir, fmt.Sprintf("opencode-cwd-%d", time.Now().UnixNano())) defer func() { os.Remove(stdoutFile) diff --git a/internal/llm/tools/sourcegraph.go b/internal/llm/tools/sourcegraph.go index a6f2c8afb..0d38c975f 100644 --- a/internal/llm/tools/sourcegraph.go +++ b/internal/llm/tools/sourcegraph.go @@ -218,7 +218,7 @@ func (t *sourcegraphTool) Run(ctx context.Context, call ToolCall) (ToolResponse, } req.Header.Set("Content-Type", "application/json") - req.Header.Set("User-Agent", "termai/1.0") + req.Header.Set("User-Agent", "opencode/1.0") resp, err := client.Do(req) if err != nil { diff --git a/internal/llm/tools/view.go b/internal/llm/tools/view.go index 7450a84bf..3fa4ca116 100644 --- a/internal/llm/tools/view.go +++ b/internal/llm/tools/view.go @@ -10,8 +10,8 @@ import ( "path/filepath" "strings" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/lsp" ) type ViewParams struct { diff --git a/internal/llm/tools/write.go b/internal/llm/tools/write.go index bb49381fd..261865c39 100644 --- a/internal/llm/tools/write.go +++ b/internal/llm/tools/write.go @@ -8,11 +8,11 @@ import ( "path/filepath" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/diff" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/diff" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/permission" ) type WriteParams struct { diff --git a/internal/llm/tools/write_test.go b/internal/llm/tools/write_test.go index 2264f36fb..b5ecb3fda 100644 --- a/internal/llm/tools/write_test.go +++ b/internal/llm/tools/write_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/kujtimiihoxha/termai/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/internal/logging/writer.go b/internal/logging/writer.go index 9fe469c5e..1dc07e853 100644 --- a/internal/logging/writer.go +++ b/internal/logging/writer.go @@ -9,7 +9,7 @@ import ( "time" "github.com/go-logfmt/logfmt" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) const ( diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 0f03e7fcb..dad07f3c0 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -13,9 +13,9 @@ import ( "sync/atomic" "time" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) type Client struct { diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index c3088d685..7a11286e6 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -3,10 +3,10 @@ package lsp import ( "encoding/json" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" - "github.com/kujtimiihoxha/termai/internal/lsp/util" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/lsp/util" ) // Requests diff --git a/internal/lsp/language.go b/internal/lsp/language.go index 2e276c464..65ccd54f3 100644 --- a/internal/lsp/language.go +++ b/internal/lsp/language.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) func DetectLanguageID(uri string) protocol.LanguageKind { diff --git a/internal/lsp/methods.go b/internal/lsp/methods.go index 079b3bfe3..ab33d7e1b 100644 --- a/internal/lsp/methods.go +++ b/internal/lsp/methods.go @@ -4,7 +4,7 @@ package lsp import ( "context" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) // Implementation sends a textDocument/implementation request to the LSP server. diff --git a/internal/lsp/transport.go b/internal/lsp/transport.go index 89255fd78..fe59b0fbb 100644 --- a/internal/lsp/transport.go +++ b/internal/lsp/transport.go @@ -8,8 +8,8 @@ import ( "io" "strings" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" ) // Write writes an LSP message to the given writer diff --git a/internal/lsp/util/edit.go b/internal/lsp/util/edit.go index 3b94fb39f..52f03ee77 100644 --- a/internal/lsp/util/edit.go +++ b/internal/lsp/util/edit.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error { diff --git a/internal/lsp/watcher/watcher.go b/internal/lsp/watcher/watcher.go index 156f38e1a..595c78db9 100644 --- a/internal/lsp/watcher/watcher.go +++ b/internal/lsp/watcher/watcher.go @@ -10,10 +10,10 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" ) // WorkspaceWatcher manages LSP file watching diff --git a/internal/message/content.go b/internal/message/content.go index f9e76b11c..f52449f4a 100644 --- a/internal/message/content.go +++ b/internal/message/content.go @@ -5,7 +5,7 @@ import ( "slices" "time" - "github.com/kujtimiihoxha/termai/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/models" ) type MessageRole string diff --git a/internal/message/message.go b/internal/message/message.go index 2871780a7..f165fcfc7 100644 --- a/internal/message/message.go +++ b/internal/message/message.go @@ -7,9 +7,9 @@ import ( "fmt" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/db" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/db" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) type CreateMessageParams struct { diff --git a/internal/permission/permission.go b/internal/permission/permission.go index 8aa280906..4cb379dea 100644 --- a/internal/permission/permission.go +++ b/internal/permission/permission.go @@ -6,7 +6,7 @@ import ( "time" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) var ErrorPermissionDenied = errors.New("permission denied") diff --git a/internal/session/session.go b/internal/session/session.go index 019019df4..280da1ff0 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -5,8 +5,8 @@ import ( "database/sql" "github.com/google/uuid" - "github.com/kujtimiihoxha/termai/internal/db" - "github.com/kujtimiihoxha/termai/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/db" + "github.com/kujtimiihoxha/opencode/internal/pubsub" ) type Session struct { diff --git a/internal/tui/components/chat/chat.go b/internal/tui/components/chat/chat.go index e98001efa..52ff4c8bf 100644 --- a/internal/tui/components/chat/chat.go +++ b/internal/tui/components/chat/chat.go @@ -5,10 +5,10 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/ansi" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/session" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/version" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/session" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/version" ) type SendMsg struct { diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go index e2f4da9e2..4d6ef5ca0 100644 --- a/internal/tui/components/chat/editor.go +++ b/internal/tui/components/chat/editor.go @@ -5,11 +5,11 @@ import ( "github.com/charmbracelet/bubbles/textarea" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/app" - "github.com/kujtimiihoxha/termai/internal/session" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/session" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type editorCmp struct { diff --git a/internal/tui/components/chat/messages.go b/internal/tui/components/chat/messages.go index 26a98970e..c2ce7d88b 100644 --- a/internal/tui/components/chat/messages.go +++ b/internal/tui/components/chat/messages.go @@ -15,17 +15,17 @@ import ( "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/ansi" - "github.com/kujtimiihoxha/termai/internal/app" - "github.com/kujtimiihoxha/termai/internal/llm/agent" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/message" - "github.com/kujtimiihoxha/termai/internal/pubsub" - "github.com/kujtimiihoxha/termai/internal/session" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/llm/agent" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/message" + "github.com/kujtimiihoxha/opencode/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/session" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type uiMessageType int diff --git a/internal/tui/components/chat/sidebar.go b/internal/tui/components/chat/sidebar.go index b90269d1a..54b39f4a1 100644 --- a/internal/tui/components/chat/sidebar.go +++ b/internal/tui/components/chat/sidebar.go @@ -7,12 +7,12 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/diff" - "github.com/kujtimiihoxha/termai/internal/history" - "github.com/kujtimiihoxha/termai/internal/pubsub" - "github.com/kujtimiihoxha/termai/internal/session" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/diff" + "github.com/kujtimiihoxha/opencode/internal/history" + "github.com/kujtimiihoxha/opencode/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/session" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type sidebarCmp struct { diff --git a/internal/tui/components/core/status.go b/internal/tui/components/core/status.go index 089dffa2c..411cac1c5 100644 --- a/internal/tui/components/core/status.go +++ b/internal/tui/components/core/status.go @@ -7,12 +7,12 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/config" - "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/lsp" - "github.com/kujtimiihoxha/termai/internal/lsp/protocol" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/config" + "github.com/kujtimiihoxha/opencode/internal/llm/models" + "github.com/kujtimiihoxha/opencode/internal/lsp" + "github.com/kujtimiihoxha/opencode/internal/lsp/protocol" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type statusCmp struct { diff --git a/internal/tui/components/dialog/help.go b/internal/tui/components/dialog/help.go index 1d3c2b077..6242017f1 100644 --- a/internal/tui/components/dialog/help.go +++ b/internal/tui/components/dialog/help.go @@ -6,7 +6,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type helpCmp struct { diff --git a/internal/tui/components/dialog/permission.go b/internal/tui/components/dialog/permission.go index 9c55effde..200a7970d 100644 --- a/internal/tui/components/dialog/permission.go +++ b/internal/tui/components/dialog/permission.go @@ -9,12 +9,12 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/diff" - "github.com/kujtimiihoxha/termai/internal/llm/tools" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/diff" + "github.com/kujtimiihoxha/opencode/internal/llm/tools" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type PermissionAction string diff --git a/internal/tui/components/dialog/quit.go b/internal/tui/components/dialog/quit.go index 10d9ba8a2..5bbe6696c 100644 --- a/internal/tui/components/dialog/quit.go +++ b/internal/tui/components/dialog/quit.go @@ -6,9 +6,9 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) const question = "Are you sure you want to quit?" diff --git a/internal/tui/components/logs/details.go b/internal/tui/components/logs/details.go index 18eb1a526..3a8f17999 100644 --- a/internal/tui/components/logs/details.go +++ b/internal/tui/components/logs/details.go @@ -9,9 +9,9 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type DetailComponent interface { diff --git a/internal/tui/components/logs/table.go b/internal/tui/components/logs/table.go index 6e8eb58b1..dc6184e3d 100644 --- a/internal/tui/components/logs/table.go +++ b/internal/tui/components/logs/table.go @@ -7,11 +7,11 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/pubsub" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type TableComponent interface { diff --git a/internal/tui/layout/border.go b/internal/tui/layout/border.go index 8fe5c430c..ea9f5e0bc 100644 --- a/internal/tui/layout/border.go +++ b/internal/tui/layout/border.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type BorderPosition int diff --git a/internal/tui/layout/container.go b/internal/tui/layout/container.go index db07d49fb..603699955 100644 --- a/internal/tui/layout/container.go +++ b/internal/tui/layout/container.go @@ -4,7 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type Container interface { diff --git a/internal/tui/layout/overlay.go b/internal/tui/layout/overlay.go index 4a1bcf661..4c05e8462 100644 --- a/internal/tui/layout/overlay.go +++ b/internal/tui/layout/overlay.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/styles" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" "github.com/mattn/go-runewidth" "github.com/muesli/ansi" "github.com/muesli/reflow/truncate" diff --git a/internal/tui/layout/split.go b/internal/tui/layout/split.go index 6482fc74c..bfb616a53 100644 --- a/internal/tui/layout/split.go +++ b/internal/tui/layout/split.go @@ -4,7 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" ) type SplitPaneLayout interface { diff --git a/internal/tui/page/chat.go b/internal/tui/page/chat.go index cebc0e461..c268e677f 100644 --- a/internal/tui/page/chat.go +++ b/internal/tui/page/chat.go @@ -5,11 +5,11 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - "github.com/kujtimiihoxha/termai/internal/app" - "github.com/kujtimiihoxha/termai/internal/session" - "github.com/kujtimiihoxha/termai/internal/tui/components/chat" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/session" + "github.com/kujtimiihoxha/opencode/internal/tui/components/chat" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) var ChatPage PageID = "chat" diff --git a/internal/tui/page/logs.go b/internal/tui/page/logs.go index d1e557eab..c77a033f4 100644 --- a/internal/tui/page/logs.go +++ b/internal/tui/page/logs.go @@ -2,8 +2,8 @@ package page import ( tea "github.com/charmbracelet/bubbletea" - "github.com/kujtimiihoxha/termai/internal/tui/components/logs" - "github.com/kujtimiihoxha/termai/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/components/logs" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" ) var LogsPage PageID = "logs" diff --git a/internal/tui/tui.go b/internal/tui/tui.go index dff7ad63d..657de6b6e 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -4,15 +4,15 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/kujtimiihoxha/termai/internal/app" - "github.com/kujtimiihoxha/termai/internal/logging" - "github.com/kujtimiihoxha/termai/internal/permission" - "github.com/kujtimiihoxha/termai/internal/pubsub" - "github.com/kujtimiihoxha/termai/internal/tui/components/core" - "github.com/kujtimiihoxha/termai/internal/tui/components/dialog" - "github.com/kujtimiihoxha/termai/internal/tui/layout" - "github.com/kujtimiihoxha/termai/internal/tui/page" - "github.com/kujtimiihoxha/termai/internal/tui/util" + "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/logging" + "github.com/kujtimiihoxha/opencode/internal/permission" + "github.com/kujtimiihoxha/opencode/internal/pubsub" + "github.com/kujtimiihoxha/opencode/internal/tui/components/core" + "github.com/kujtimiihoxha/opencode/internal/tui/components/dialog" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/page" + "github.com/kujtimiihoxha/opencode/internal/tui/util" ) type keyMap struct { diff --git a/main.go b/main.go index 2e6954646..06578c7ef 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( - "github.com/kujtimiihoxha/termai/cmd" - "github.com/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/opencode/cmd" + "github.com/kujtimiihoxha/opencode/internal/logging" ) func main() { -- cgit v1.2.3 From 3a6a26981a8074b6ab0eaadb520db986e04799ff Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Mon, 21 Apr 2025 19:48:36 +0200 Subject: init command --- README.md | 55 ++++--- internal/config/init.go | 61 +++++++ internal/llm/agent/mcp-tools.go | 5 + internal/llm/prompt/prompt.go | 16 +- internal/llm/prompt/title.go | 1 + internal/llm/tools/bash.go | 8 +- internal/llm/tools/edit.go | 3 + internal/llm/tools/fetch.go | 6 + internal/llm/tools/patch.go | 3 + internal/llm/tools/write.go | 1 + internal/permission/permission.go | 16 +- internal/tui/components/dialog/commands.go | 247 +++++++++++++++++++++++++++++ internal/tui/components/dialog/init.go | 191 ++++++++++++++++++++++ internal/tui/tui.go | 175 +++++++++++++++++++- 14 files changed, 753 insertions(+), 35 deletions(-) create mode 100644 internal/config/init.go create mode 100644 internal/tui/components/dialog/commands.go create mode 100644 internal/tui/components/dialog/init.go (limited to 'internal/llm/tools/fetch.go') diff --git a/README.md b/README.md index 7e8c2791b..145a881f5 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ You can configure OpenCode using environment variables: OpenCode supports a variety of AI models from different providers: ### OpenAI + - GPT-4.1 family (gpt-4.1, gpt-4.1-mini, gpt-4.1-nano) - GPT-4.5 Preview - GPT-4o family (gpt-4o, gpt-4o-mini) @@ -112,6 +113,7 @@ OpenCode supports a variety of AI models from different providers: - O4 Mini ### Anthropic + - Claude 3.5 Sonnet - Claude 3.5 Haiku - Claude 3.7 Sonnet @@ -119,12 +121,14 @@ OpenCode supports a variety of AI models from different providers: - Claude 3 Opus ### Google + - Gemini 2.5 - Gemini 2.5 Flash - Gemini 2.0 Flash - Gemini 2.0 Flash Lite ### AWS Bedrock + - Claude 3.7 Sonnet ## Usage @@ -152,14 +156,15 @@ opencode -c /path/to/project ### Global Shortcuts -| Shortcut | Action | -| --------- | ------------------------------------------------------- | -| `Ctrl+C` | Quit application | -| `Ctrl+?` | Toggle help dialog | -| `?` | Toggle help dialog (when not in editing mode) | -| `Ctrl+L` | View logs | -| `Ctrl+A` | Switch session | -| `Esc` | Close current overlay/dialog or return to previous mode | +| Shortcut | Action | +| -------- | ------------------------------------------------------- | +| `Ctrl+C` | Quit application | +| `Ctrl+?` | Toggle help dialog | +| `?` | Toggle help dialog (when not in editing mode) | +| `Ctrl+L` | View logs | +| `Ctrl+A` | Switch session | +| `Ctrl+K` | Command dialog | +| `Esc` | Close current overlay/dialog or return to previous mode | ### Chat Page Shortcuts @@ -181,28 +186,28 @@ opencode -c /path/to/project ### Session Dialog Shortcuts -| Shortcut | Action | -| ------------- | ---------------- | -| `↑` or `k` | Previous session | -| `↓` or `j` | Next session | -| `Enter` | Select session | -| `Esc` | Close dialog | +| Shortcut | Action | +| ---------- | ---------------- | +| `↑` or `k` | Previous session | +| `↓` or `j` | Next session | +| `Enter` | Select session | +| `Esc` | Close dialog | ### Permission Dialog Shortcuts -| Shortcut | Action | -| ------------------------- | ----------------------- | -| `←` or `left` | Switch options left | -| `→` or `right` or `tab` | Switch options right | -| `Enter` or `space` | Confirm selection | -| `a` | Allow permission | -| `A` | Allow permission for session | -| `d` | Deny permission | +| Shortcut | Action | +| ----------------------- | ---------------------------- | +| `←` or `left` | Switch options left | +| `→` or `right` or `tab` | Switch options right | +| `Enter` or `space` | Confirm selection | +| `a` | Allow permission | +| `A` | Allow permission for session | +| `d` | Deny permission | ### Logs Page Shortcuts -| Shortcut | Action | -| ---------------- | ------------------- | +| Shortcut | Action | +| ------------------ | ------------------- | | `Backspace` or `q` | Return to chat page | ## AI Assistant Tools @@ -369,4 +374,4 @@ Contributions are welcome! Here's how you can contribute: 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request -Please make sure to update tests as appropriate and follow the existing code style. \ No newline at end of file +Please make sure to update tests as appropriate and follow the existing code style. diff --git a/internal/config/init.go b/internal/config/init.go new file mode 100644 index 000000000..e0a1c6da7 --- /dev/null +++ b/internal/config/init.go @@ -0,0 +1,61 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + // InitFlagFilename is the name of the file that indicates whether the project has been initialized + InitFlagFilename = "init" +) + +// ProjectInitFlag represents the initialization status for a project directory +type ProjectInitFlag struct { + Initialized bool `json:"initialized"` +} + +// ShouldShowInitDialog checks if the initialization dialog should be shown for the current directory +func ShouldShowInitDialog() (bool, error) { + if cfg == nil { + return false, fmt.Errorf("config not loaded") + } + + // Create the flag file path + flagFilePath := filepath.Join(cfg.Data.Directory, InitFlagFilename) + + // Check if the flag file exists + _, err := os.Stat(flagFilePath) + if err == nil { + // File exists, don't show the dialog + return false, nil + } + + // If the error is not "file not found", return the error + if !os.IsNotExist(err) { + return false, fmt.Errorf("failed to check init flag file: %w", err) + } + + // File doesn't exist, show the dialog + return true, nil +} + +// MarkProjectInitialized marks the current project as initialized +func MarkProjectInitialized() error { + if cfg == nil { + return fmt.Errorf("config not loaded") + } + // Create the flag file path + flagFilePath := filepath.Join(cfg.Data.Directory, InitFlagFilename) + + // Create an empty file to mark the project as initialized + file, err := os.Create(flagFilePath) + if err != nil { + return fmt.Errorf("failed to create init flag file: %w", err) + } + defer file.Close() + + return nil +} + diff --git a/internal/llm/agent/mcp-tools.go b/internal/llm/agent/mcp-tools.go index 16dddc1ba..53aada33f 100644 --- a/internal/llm/agent/mcp-tools.go +++ b/internal/llm/agent/mcp-tools.go @@ -80,9 +80,14 @@ func runTool(ctx context.Context, c MCPClient, toolName string, input string) (t } func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) { + sessionID, messageID := tools.GetContextValues(ctx) + if sessionID == "" || messageID == "" { + return tools.ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file") + } permissionDescription := fmt.Sprintf("execute %s with the following parameters: %s", b.Info().Name, params.Input) p := b.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: config.WorkingDirectory(), ToolName: b.Info().Name, Action: "execute", diff --git a/internal/llm/prompt/prompt.go b/internal/llm/prompt/prompt.go index cf4d9a7e7..a6b4c03fb 100644 --- a/internal/llm/prompt/prompt.go +++ b/internal/llm/prompt/prompt.go @@ -14,8 +14,13 @@ var contextFiles = []string{ ".github/copilot-instructions.md", ".cursorrules", "CLAUDE.md", + "CLAUDE.local.md", "opencode.md", + "opencode.local.md", "OpenCode.md", + "OpenCode.local.md", + "OPENCODE.md", + "OPENCODE.local.md", } func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string { @@ -31,12 +36,13 @@ func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) s basePrompt = "You are a helpful assistant" } - // Add context from project-specific instruction files if they exist - contextContent := getContextFromFiles() - if contextContent != "" { - return fmt.Sprintf("%s\n\n# Project-Specific Context\n%s", basePrompt, contextContent) + if agentName == config.AgentCoder || agentName == config.AgentTask { + // Add context from project-specific instruction files if they exist + contextContent := getContextFromFiles() + if contextContent != "" { + return fmt.Sprintf("%s\n\n# Project-Specific Context\n%s", basePrompt, contextContent) + } } - return basePrompt } diff --git a/internal/llm/prompt/title.go b/internal/llm/prompt/title.go index 6e5289b24..5656360da 100644 --- a/internal/llm/prompt/title.go +++ b/internal/llm/prompt/title.go @@ -6,6 +6,7 @@ func TitlePrompt(_ models.ModelProvider) string { return `you will generate a short title based on the first message a user begins a conversation with - ensure it is not more than 50 characters long - the title should be a summary of the user's message +- it should be one line long - do not use quotes or colons - the entire text you return will be used as the title` } diff --git a/internal/llm/tools/bash.go b/internal/llm/tools/bash.go index 18533b761..a17506197 100644 --- a/internal/llm/tools/bash.go +++ b/internal/llm/tools/bash.go @@ -51,7 +51,7 @@ var safeReadOnlyCommands = []string{ "git status", "git log", "git diff", "git show", "git branch", "git tag", "git remote", "git ls-files", "git ls-remote", "git rev-parse", "git config --get", "git config --list", "git describe", "git blame", "git grep", "git shortlog", - "go version", "go list", "go env", "go doc", "go vet", "go fmt", "go mod", "go test", "go build", "go run", "go install", "go clean", + "go version", "go help", "go list", "go env", "go doc", "go vet", "go fmt", "go mod", "go test", "go build", "go run", "go install", "go clean", } func bashDescription() string { @@ -261,9 +261,15 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) } } } + + sessionID, messageID := GetContextValues(ctx) + if sessionID == "" || messageID == "" { + return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file") + } if !isSafeReadOnly { p := b.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: config.WorkingDirectory(), ToolName: BashToolName, Action: "execute", diff --git a/internal/llm/tools/edit.go b/internal/llm/tools/edit.go index 23c44399b..e2e257875 100644 --- a/internal/llm/tools/edit.go +++ b/internal/llm/tools/edit.go @@ -203,6 +203,7 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string) } p := e.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: permissionPath, ToolName: EditToolName, Action: "write", @@ -313,6 +314,7 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string } p := e.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: permissionPath, ToolName: EditToolName, Action: "write", @@ -432,6 +434,7 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS } p := e.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: permissionPath, ToolName: EditToolName, Action: "write", diff --git a/internal/llm/tools/fetch.go b/internal/llm/tools/fetch.go index 827755863..47ff03e57 100644 --- a/internal/llm/tools/fetch.go +++ b/internal/llm/tools/fetch.go @@ -116,8 +116,14 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error return NewTextErrorResponse("URL must start with http:// or https://"), nil } + sessionID, messageID := GetContextValues(ctx) + if sessionID == "" || messageID == "" { + return ToolResponse{}, fmt.Errorf("session ID and message ID are required for creating a new file") + } + p := t.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: config.WorkingDirectory(), ToolName: FetchToolName, Action: "fetch", diff --git a/internal/llm/tools/patch.go b/internal/llm/tools/patch.go index 903404497..7e20e378e 100644 --- a/internal/llm/tools/patch.go +++ b/internal/llm/tools/patch.go @@ -194,6 +194,7 @@ func (p *patchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error patchDiff, _, _ := diff.GenerateDiff("", *change.NewContent, path) p := p.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: dir, ToolName: PatchToolName, Action: "create", @@ -220,6 +221,7 @@ func (p *patchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error dir := filepath.Dir(path) p := p.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: dir, ToolName: PatchToolName, Action: "update", @@ -238,6 +240,7 @@ func (p *patchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error patchDiff, _, _ := diff.GenerateDiff(*change.OldContent, "", path) p := p.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: dir, ToolName: PatchToolName, Action: "delete", diff --git a/internal/llm/tools/write.go b/internal/llm/tools/write.go index 3a94b47b6..ec6fc1dc4 100644 --- a/internal/llm/tools/write.go +++ b/internal/llm/tools/write.go @@ -168,6 +168,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error } p := w.permissions.Request( permission.CreatePermissionRequest{ + SessionID: sessionID, Path: permissionPath, ToolName: WriteToolName, Action: "write", diff --git a/internal/permission/permission.go b/internal/permission/permission.go index 06f69a33d..f36efea65 100644 --- a/internal/permission/permission.go +++ b/internal/permission/permission.go @@ -3,6 +3,7 @@ package permission import ( "errors" "path/filepath" + "slices" "sync" "time" @@ -14,6 +15,7 @@ import ( var ErrorPermissionDenied = errors.New("permission denied") type CreatePermissionRequest struct { + SessionID string `json:"session_id"` ToolName string `json:"tool_name"` Description string `json:"description"` Action string `json:"action"` @@ -37,13 +39,15 @@ type Service interface { Grant(permission PermissionRequest) Deny(permission PermissionRequest) Request(opts CreatePermissionRequest) bool + AutoApproveSession(sessionID string) } type permissionService struct { *pubsub.Broker[PermissionRequest] - sessionPermissions []PermissionRequest - pendingRequests sync.Map + sessionPermissions []PermissionRequest + pendingRequests sync.Map + autoApproveSessions []string } func (s *permissionService) GrantPersistant(permission PermissionRequest) { @@ -69,6 +73,9 @@ func (s *permissionService) Deny(permission PermissionRequest) { } func (s *permissionService) Request(opts CreatePermissionRequest) bool { + if slices.Contains(s.autoApproveSessions, opts.SessionID) { + return true + } dir := filepath.Dir(opts.Path) if dir == "." { dir = config.WorkingDirectory() @@ -76,6 +83,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool { permission := PermissionRequest{ ID: uuid.New().String(), Path: dir, + SessionID: opts.SessionID, ToolName: opts.ToolName, Description: opts.Description, Action: opts.Action, @@ -104,6 +112,10 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool { } } +func (s *permissionService) AutoApproveSession(sessionID string) { + s.autoApproveSessions = append(s.autoApproveSessions, sessionID) +} + func NewPermissionService() Service { return &permissionService{ Broker: pubsub.NewBroker[PermissionRequest](), diff --git a/internal/tui/components/dialog/commands.go b/internal/tui/components/dialog/commands.go new file mode 100644 index 000000000..7b25caeb0 --- /dev/null +++ b/internal/tui/components/dialog/commands.go @@ -0,0 +1,247 @@ +package dialog + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kujtimiihoxha/opencode/internal/tui/layout" + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" +) + +// Command represents a command that can be executed +type Command struct { + ID string + Title string + Description string + Handler func(cmd Command) tea.Cmd +} + +// CommandSelectedMsg is sent when a command is selected +type CommandSelectedMsg struct { + Command Command +} + +// CloseCommandDialogMsg is sent when the command dialog is closed +type CloseCommandDialogMsg struct{} + +// CommandDialog interface for the command selection dialog +type CommandDialog interface { + tea.Model + layout.Bindings + SetCommands(commands []Command) + SetSelectedCommand(commandID string) +} + +type commandDialogCmp struct { + commands []Command + selectedIdx int + width int + height int + selectedCommandID string +} + +type commandKeyMap struct { + Up key.Binding + Down key.Binding + Enter key.Binding + Escape key.Binding + J key.Binding + K key.Binding +} + +var commandKeys = commandKeyMap{ + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "previous command"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "next command"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select command"), + ), + Escape: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "close"), + ), + J: key.NewBinding( + key.WithKeys("j"), + key.WithHelp("j", "next command"), + ), + K: key.NewBinding( + key.WithKeys("k"), + key.WithHelp("k", "previous command"), + ), +} + +func (c *commandDialogCmp) Init() tea.Cmd { + return nil +} + +func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, commandKeys.Up) || key.Matches(msg, commandKeys.K): + if c.selectedIdx > 0 { + c.selectedIdx-- + } + return c, nil + case key.Matches(msg, commandKeys.Down) || key.Matches(msg, commandKeys.J): + if c.selectedIdx < len(c.commands)-1 { + c.selectedIdx++ + } + return c, nil + case key.Matches(msg, commandKeys.Enter): + if len(c.commands) > 0 { + return c, util.CmdHandler(CommandSelectedMsg{ + Command: c.commands[c.selectedIdx], + }) + } + case key.Matches(msg, commandKeys.Escape): + return c, util.CmdHandler(CloseCommandDialogMsg{}) + } + case tea.WindowSizeMsg: + c.width = msg.Width + c.height = msg.Height + } + return c, nil +} + +func (c *commandDialogCmp) View() string { + if len(c.commands) == 0 { + return styles.BaseStyle.Padding(1, 2). + Border(lipgloss.RoundedBorder()). + BorderBackground(styles.Background). + BorderForeground(styles.ForgroundDim). + Width(40). + Render("No commands available") + } + + // Calculate max width needed for command titles + maxWidth := 40 // Minimum width + for _, cmd := range c.commands { + if len(cmd.Title) > maxWidth-4 { // Account for padding + maxWidth = len(cmd.Title) + 4 + } + if len(cmd.Description) > maxWidth-4 { + maxWidth = len(cmd.Description) + 4 + } + } + + // Limit height to avoid taking up too much screen space + maxVisibleCommands := min(10, len(c.commands)) + + // Build the command list + commandItems := make([]string, 0, maxVisibleCommands) + startIdx := 0 + + // If we have more commands than can be displayed, adjust the start index + if len(c.commands) > maxVisibleCommands { + // Center the selected item when possible + halfVisible := maxVisibleCommands / 2 + if c.selectedIdx >= halfVisible && c.selectedIdx < len(c.commands)-halfVisible { + startIdx = c.selectedIdx - halfVisible + } else if c.selectedIdx >= len(c.commands)-halfVisible { + startIdx = len(c.commands) - maxVisibleCommands + } + } + + endIdx := min(startIdx+maxVisibleCommands, len(c.commands)) + + for i := startIdx; i < endIdx; i++ { + cmd := c.commands[i] + itemStyle := styles.BaseStyle.Width(maxWidth) + descStyle := styles.BaseStyle.Width(maxWidth).Foreground(styles.ForgroundDim) + + if i == c.selectedIdx { + itemStyle = itemStyle. + Background(styles.PrimaryColor). + Foreground(styles.Background). + Bold(true) + descStyle = descStyle. + Background(styles.PrimaryColor). + Foreground(styles.Background) + } + + title := itemStyle.Padding(0, 1).Render(cmd.Title) + description := "" + if cmd.Description != "" { + description = descStyle.Padding(0, 1).Render(cmd.Description) + commandItems = append(commandItems, lipgloss.JoinVertical(lipgloss.Left, title, description)) + } else { + commandItems = append(commandItems, title) + } + } + + title := styles.BaseStyle. + Foreground(styles.PrimaryColor). + Bold(true). + Width(maxWidth). + Padding(0, 1). + Render("Commands") + + content := lipgloss.JoinVertical( + lipgloss.Left, + title, + styles.BaseStyle.Width(maxWidth).Render(""), + styles.BaseStyle.Width(maxWidth).Render(lipgloss.JoinVertical(lipgloss.Left, commandItems...)), + styles.BaseStyle.Width(maxWidth).Render(""), + styles.BaseStyle.Width(maxWidth).Padding(0, 1).Foreground(styles.ForgroundDim).Render("↑/k: up ↓/j: down enter: select esc: cancel"), + ) + + return styles.BaseStyle.Padding(1, 2). + Border(lipgloss.RoundedBorder()). + BorderBackground(styles.Background). + BorderForeground(styles.ForgroundDim). + Width(lipgloss.Width(content) + 4). + Render(content) +} + +func (c *commandDialogCmp) BindingKeys() []key.Binding { + return layout.KeyMapToSlice(commandKeys) +} + +func (c *commandDialogCmp) SetCommands(commands []Command) { + c.commands = commands + + // If we have a selected command ID, find its index + if c.selectedCommandID != "" { + for i, cmd := range commands { + if cmd.ID == c.selectedCommandID { + c.selectedIdx = i + return + } + } + } + + // Default to first command if selected not found + c.selectedIdx = 0 +} + +func (c *commandDialogCmp) SetSelectedCommand(commandID string) { + c.selectedCommandID = commandID + + // Update the selected index if commands are already loaded + if len(c.commands) > 0 { + for i, cmd := range c.commands { + if cmd.ID == commandID { + c.selectedIdx = i + return + } + } + } +} + +// NewCommandDialogCmp creates a new command selection dialog +func NewCommandDialogCmp() CommandDialog { + return &commandDialogCmp{ + commands: []Command{}, + selectedIdx: 0, + selectedCommandID: "", + } +} + diff --git a/internal/tui/components/dialog/init.go b/internal/tui/components/dialog/init.go new file mode 100644 index 000000000..6098ca755 --- /dev/null +++ b/internal/tui/components/dialog/init.go @@ -0,0 +1,191 @@ +package dialog + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + + "github.com/kujtimiihoxha/opencode/internal/tui/styles" + "github.com/kujtimiihoxha/opencode/internal/tui/util" +) + +// InitDialogCmp is a component that asks the user if they want to initialize the project. +type InitDialogCmp struct { + width, height int + selected int + keys initDialogKeyMap +} + +// NewInitDialogCmp creates a new InitDialogCmp. +func NewInitDialogCmp() InitDialogCmp { + return InitDialogCmp{ + selected: 0, + keys: initDialogKeyMap{}, + } +} + +type initDialogKeyMap struct { + Tab key.Binding + Left key.Binding + Right key.Binding + Enter key.Binding + Escape key.Binding + Y key.Binding + N key.Binding +} + +// ShortHelp implements key.Map. +func (k initDialogKeyMap) ShortHelp() []key.Binding { + return []key.Binding{ + key.NewBinding( + key.WithKeys("tab", "left", "right"), + key.WithHelp("tab/←/→", "toggle selection"), + ), + key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "confirm"), + ), + key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "cancel"), + ), + key.NewBinding( + key.WithKeys("y", "n"), + key.WithHelp("y/n", "yes/no"), + ), + } +} + +// FullHelp implements key.Map. +func (k initDialogKeyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{k.ShortHelp()} +} + +// Init implements tea.Model. +func (m InitDialogCmp) Init() tea.Cmd { + return nil +} + +// Update implements tea.Model. +func (m InitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, key.NewBinding(key.WithKeys("esc"))): + return m, util.CmdHandler(CloseInitDialogMsg{Initialize: false}) + case key.Matches(msg, key.NewBinding(key.WithKeys("tab", "left", "right", "h", "l"))): + m.selected = (m.selected + 1) % 2 + return m, nil + case key.Matches(msg, key.NewBinding(key.WithKeys("enter"))): + return m, util.CmdHandler(CloseInitDialogMsg{Initialize: m.selected == 0}) + case key.Matches(msg, key.NewBinding(key.WithKeys("y"))): + return m, util.CmdHandler(CloseInitDialogMsg{Initialize: true}) + case key.Matches(msg, key.NewBinding(key.WithKeys("n"))): + return m, util.CmdHandler(CloseInitDialogMsg{Initialize: false}) + } + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + } + return m, nil +} + +// View implements tea.Model. +func (m InitDialogCmp) View() string { + // Calculate width needed for content + maxWidth := 60 // Width for explanation text + + title := styles.BaseStyle. + Foreground(styles.PrimaryColor). + Bold(true). + Width(maxWidth). + Padding(0, 1). + Render("Initialize Project") + + explanation := styles.BaseStyle. + Foreground(styles.Forground). + Width(maxWidth). + Padding(0, 1). + Render("Initialization generates a new OpenCode.md file that contains information about your codebase, this file serves as memory for each project, you can freely add to it to help the agents be better at their job.") + + question := styles.BaseStyle. + Foreground(styles.Forground). + Width(maxWidth). + Padding(1, 1). + Render("Would you like to initialize this project?") + + yesStyle := styles.BaseStyle + noStyle := styles.BaseStyle + + if m.selected == 0 { + yesStyle = yesStyle. + Background(styles.PrimaryColor). + Foreground(styles.Background). + Bold(true) + noStyle = noStyle. + Background(styles.Background). + Foreground(styles.PrimaryColor) + } else { + noStyle = noStyle. + Background(styles.PrimaryColor). + Foreground(styles.Background). + Bold(true) + yesStyle = yesStyle. + Background(styles.Background). + Foreground(styles.PrimaryColor) + } + + yes := yesStyle.Padding(0, 3).Render("Yes") + no := noStyle.Padding(0, 3).Render("No") + + buttons := lipgloss.JoinHorizontal(lipgloss.Center, yes, styles.BaseStyle.Render(" "), no) + buttons = styles.BaseStyle. + Width(maxWidth). + Padding(1, 0). + Render(buttons) + + help := styles.BaseStyle. + Width(maxWidth). + Padding(0, 1). + Foreground(styles.ForgroundDim). + Render("tab/←/→: toggle y/n: yes/no enter: confirm esc: cancel") + + content := lipgloss.JoinVertical( + lipgloss.Left, + title, + styles.BaseStyle.Width(maxWidth).Render(""), + explanation, + question, + buttons, + styles.BaseStyle.Width(maxWidth).Render(""), + help, + ) + + return styles.BaseStyle.Padding(1, 2). + Border(lipgloss.RoundedBorder()). + BorderBackground(styles.Background). + BorderForeground(styles.ForgroundDim). + Width(lipgloss.Width(content) + 4). + Render(content) +} + +// SetSize sets the size of the component. +func (m *InitDialogCmp) SetSize(width, height int) { + m.width = width + m.height = height +} + +// Bindings implements layout.Bindings. +func (m InitDialogCmp) Bindings() []key.Binding { + return m.keys.ShortHelp() +} + +// CloseInitDialogMsg is a message that is sent when the init dialog is closed. +type CloseInitDialogMsg struct { + Initialize bool +} + +// ShowInitDialogMsg is a message that is sent to show the init dialog. +type ShowInitDialogMsg struct { + Show bool +} diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 392b9ec41..4a723d40d 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -7,6 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/kujtimiihoxha/opencode/internal/app" + "github.com/kujtimiihoxha/opencode/internal/config" "github.com/kujtimiihoxha/opencode/internal/logging" "github.com/kujtimiihoxha/opencode/internal/permission" "github.com/kujtimiihoxha/opencode/internal/pubsub" @@ -23,6 +24,7 @@ type keyMap struct { Quit key.Binding Help key.Binding SwitchSession key.Binding + Commands key.Binding } var keys = keyMap{ @@ -44,6 +46,11 @@ var keys = keyMap{ key.WithKeys("ctrl+a"), key.WithHelp("ctrl+a", "switch session"), ), + + Commands: key.NewBinding( + key.WithKeys("ctrl+k"), + key.WithHelp("ctrl+K", "commands"), + ), } var helpEsc = key.NewBinding( @@ -82,6 +89,13 @@ type appModel struct { showSessionDialog bool sessionDialog dialog.SessionDialog + showCommandDialog bool + commandDialog dialog.CommandDialog + commands []dialog.Command + + showInitDialog bool + initDialog dialog.InitDialogCmp + editingMode bool } @@ -98,6 +112,23 @@ func (a appModel) Init() tea.Cmd { cmds = append(cmds, cmd) cmd = a.sessionDialog.Init() cmds = append(cmds, cmd) + cmd = a.commandDialog.Init() + cmds = append(cmds, cmd) + cmd = a.initDialog.Init() + cmds = append(cmds, cmd) + + // Check if we should show the init dialog + cmds = append(cmds, func() tea.Msg { + shouldShow, err := config.ShouldShowInitDialog() + if err != nil { + return util.InfoMsg{ + Type: util.InfoTypeError, + Msg: "Failed to check init status: " + err.Error(), + } + } + return dialog.ShowInitDialogMsg{Show: shouldShow} + }) + return tea.Batch(cmds...) } @@ -126,6 +157,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.sessionDialog = session.(dialog.SessionDialog) cmds = append(cmds, sessionCmd) + command, commandCmd := a.commandDialog.Update(msg) + a.commandDialog = command.(dialog.CommandDialog) + cmds = append(cmds, commandCmd) + + a.initDialog.SetSize(msg.Width, msg.Height) + return a, tea.Batch(cmds...) case chat.EditorFocusMsg: a.editingMode = bool(msg) @@ -207,6 +244,35 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.showSessionDialog = false return a, nil + case dialog.CloseCommandDialogMsg: + a.showCommandDialog = false + return a, nil + + case dialog.ShowInitDialogMsg: + a.showInitDialog = msg.Show + return a, nil + + case dialog.CloseInitDialogMsg: + a.showInitDialog = false + if msg.Initialize { + // Run the initialization command + for _, cmd := range a.commands { + if cmd.ID == "init" { + // Mark the project as initialized + if err := config.MarkProjectInitialized(); err != nil { + return a, util.ReportError(err) + } + return a, cmd.Handler(cmd) + } + } + } else { + // Mark the project as initialized without running the command + if err := config.MarkProjectInitialized(); err != nil { + return a, util.ReportError(err) + } + } + return a, nil + case chat.SessionSelectedMsg: a.sessionDialog.SetSelectedSession(msg.ID) case dialog.SessionSelectedMsg: @@ -216,6 +282,14 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return a, nil + case dialog.CommandSelectedMsg: + a.showCommandDialog = false + // Execute the command handler if available + if msg.Command.Handler != nil { + return a, msg.Command.Handler(msg.Command) + } + return a, util.ReportInfo("Command selected: " + msg.Command.Title) + case tea.KeyMsg: switch { case key.Matches(msg, keys.Quit): @@ -226,9 +300,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if a.showSessionDialog { a.showSessionDialog = false } + if a.showCommandDialog { + a.showCommandDialog = false + } return a, nil case key.Matches(msg, keys.SwitchSession): - if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions { + if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions && !a.showCommandDialog { // Load sessions and show the dialog sessions, err := a.app.Sessions.List(context.Background()) if err != nil { @@ -242,6 +319,17 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, nil } return a, nil + case key.Matches(msg, keys.Commands): + if a.currentPage == page.ChatPage && !a.showQuit && !a.showPermissions && !a.showSessionDialog { + // Show commands dialog + if len(a.commands) == 0 { + return a, util.ReportWarn("No commands available") + } + a.commandDialog.SetCommands(a.commands) + a.showCommandDialog = true + return a, nil + } + return a, nil case key.Matches(msg, logsKeyReturnKey): if a.currentPage == page.LogsPage { return a, a.moveToPage(page.ChatPage) @@ -255,6 +343,14 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.showHelp = !a.showHelp return a, nil } + if a.showInitDialog { + a.showInitDialog = false + // Mark the project as initialized without running the command + if err := config.MarkProjectInitialized(); err != nil { + return a, util.ReportError(err) + } + return a, nil + } case key.Matches(msg, keys.Logs): return a, a.moveToPage(page.LogsPage) case key.Matches(msg, keys.Help): @@ -304,6 +400,26 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + if a.showCommandDialog { + d, commandCmd := a.commandDialog.Update(msg) + a.commandDialog = d.(dialog.CommandDialog) + cmds = append(cmds, commandCmd) + // Only block key messages send all other messages down + if _, ok := msg.(tea.KeyMsg); ok { + return a, tea.Batch(cmds...) + } + } + + if a.showInitDialog { + d, initCmd := a.initDialog.Update(msg) + a.initDialog = d.(dialog.InitDialogCmp) + cmds = append(cmds, initCmd) + // Only block key messages send all other messages down + if _, ok := msg.(tea.KeyMsg); ok { + return a, tea.Batch(cmds...) + } + } + s, _ := a.status.Update(msg) a.status = s.(core.StatusCmp) a.pages[a.currentPage], cmd = a.pages[a.currentPage].Update(msg) @@ -311,6 +427,11 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, tea.Batch(cmds...) } +// RegisterCommand adds a command to the command dialog +func (a *appModel) RegisterCommand(cmd dialog.Command) { + a.commands = append(a.commands, cmd) +} + func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd { if a.app.CoderAgent.IsBusy() { // For now we don't move to any page if the agent is busy @@ -422,24 +543,74 @@ func (a appModel) View() string { ) } + if a.showCommandDialog { + overlay := a.commandDialog.View() + row := lipgloss.Height(appView) / 2 + row -= lipgloss.Height(overlay) / 2 + col := lipgloss.Width(appView) / 2 + col -= lipgloss.Width(overlay) / 2 + appView = layout.PlaceOverlay( + col, + row, + overlay, + appView, + true, + ) + } + + if a.showInitDialog { + overlay := a.initDialog.View() + appView = layout.PlaceOverlay( + a.width/2-lipgloss.Width(overlay)/2, + a.height/2-lipgloss.Height(overlay)/2, + overlay, + appView, + true, + ) + } + return appView } func New(app *app.App) tea.Model { startPage := page.ChatPage - return &appModel{ + model := &appModel{ currentPage: startPage, loadedPages: make(map[page.PageID]bool), status: core.NewStatusCmp(app.LSPClients), help: dialog.NewHelpCmp(), quit: dialog.NewQuitCmp(), sessionDialog: dialog.NewSessionDialogCmp(), + commandDialog: dialog.NewCommandDialogCmp(), permissions: dialog.NewPermissionDialogCmp(), + initDialog: dialog.NewInitDialogCmp(), app: app, editingMode: true, + commands: []dialog.Command{}, pages: map[page.PageID]tea.Model{ page.ChatPage: page.NewChatPage(app), page.LogsPage: page.NewLogsPage(), }, } + + model.RegisterCommand(dialog.Command{ + ID: "init", + Title: "Initialize Project", + Description: "Create/Update the OpenCode.md memory file", + Handler: func(cmd dialog.Command) tea.Cmd { + prompt := `Please analyze this codebase and create a OpenCode.md file containing: +1. Build/lint/test commands - especially for running a single test +2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc. + +The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long. +If there's already a opencode.md, improve it. +If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.` + return tea.Batch( + util.CmdHandler(chat.SendMsg{ + Text: prompt, + }), + ) + }, + }) + return model } -- cgit v1.2.3