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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.22.x']
go-version: ['1.26.x']

steps:
- uses: actions/checkout@v4
Expand All @@ -30,8 +30,16 @@ jobs:
run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi
if: runner.os == 'Linux'

- name: Vet
run: go vet ./...

- name: Run Tests
run: go test -v -race -timeout 10m ./...
if: runner.os != 'Windows'

- name: Run Tests
run: go test -v ./...
run: go test -v -timeout 10m ./...
if: runner.os == 'Windows'

- name: Build
run: go build -v ./...
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22.x'
go-version: '1.26.x'

- name: Install dependencies
run: go mod download
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22.x'
go-version: '1.26.x'

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
Expand Down
16 changes: 8 additions & 8 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ builds:
- CGO_ENABLED=0
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.ShortCommit}}
- -X main.date={{.Date}}
- -X main.builtBy=goreleaser
- -X github.com/Nithwin/WindMist/cmd.Version={{.Version}}
- -X github.com/Nithwin/WindMist/cmd.Commit={{.ShortCommit}}
- -X github.com/Nithwin/WindMist/cmd.Date={{.Date}}

archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
- formats: [tar.gz]
name_template: >-
{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ title .Os }}{{ end }}_{{ if eq .Arch "amd64" }}x86_64{{ else }}{{ .Arch }}{{ end }}
format_overrides:
- goos: windows
format: zip
formats: [zip]

checksum:
name_template: "checksums.txt"
Expand All @@ -52,4 +52,4 @@ brews:
homepage: "https://github.com/Nithwin/WindMist"
description: "AI coding agent for the terminal."
test: |
system "#{bin}/windmist --version"
system "#{bin}/windmist", "version"
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [v2.0.1] - 2026-08-06

### Fixed
- **Release packaging**: GoReleaser archive names now match `install.sh` / npm (`Linux`/`macOS`/`Windows` + `x86_64`), and ldflags correctly inject `cmd.Version` / `Commit` / `Date`.
- **Gemini tool calling**: Thought signatures are attached to the first `functionCall` Part (required by Gemini 3); removed invalid signatures on tool-response Parts.
- **Gemini streaming**: Removed the hard 60s HTTP client timeout that aborted long generations.
- **RAG indexing**: `IndexProject(".")` no longer skips the entire workspace; vocabulary ranking is deterministic and vectors are re-embedded after rebuild.
- **Sessions**: Absolute project paths on `/new`, nanosecond session IDs, and race-free async title updates via the Bubble Tea main loop.
- **Agent lifecycle**: Provider/model switches close the previous agent (MCP/LSP) before creating a new one; session/mode changes use `Reconfigure` instead of leaking processes.
- **Remote `/provider`**: No longer wipes the configured model when switching providers.
- **MCP**: Safe type assertions when parsing tool schemas; array `items` types are preserved for Gemini.
- **Uninstall `--purge`**: Also removes `~/.windmist` (sessions + RAG), not only `~/.config/windmist`.
- **CI**: Go 1.26, `go vet`, and race tests aligned with `go.mod` / CHANGELOG claims.
- **Docs/install**: Correct module path and `go build` instructions in README.

### Changed
- **Token efficiency (free-tier friendly)**: New `chat` mode for greetings/simple questions (no tools, tiny system prompt). Auto-router uses local heuristics first so "hi" no longer burns a classify call or drops into plan mode. System prompts collapsed; repo map capped at 80 files; history/context windows tightened.

---

## [v2.0.0] - 2026-07-27

### Added
Expand Down
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<div align="center">

# 🌀 WindMist `v2.0.0`
# 🌀 WindMist `v2.0.1`
### Autonomous AI Software Engineer Running Directly in Your Terminal

<img src="images/Gemini_Generated_Image_4fucu04fucu04fuc.png?v=2" alt="WindMist Hero Banner" width="860" />

<br/>

[![Version: v2.0.0](https://img.shields.io/badge/Version-v2.0.0-8B5CF6?style=for-the-badge)](CHANGELOG.md)
[![Version: v2.0.1](https://img.shields.io/badge/Version-v2.0.1-8B5CF6?style=for-the-badge)](CHANGELOG.md)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE)
[![Go Version](https://img.shields.io/badge/Go-1.26+-00ADD8?style=for-the-badge&logo=go)](https://golang.org)
[![Discord](https://img.shields.io/badge/Discord-Join-7289DA?style=for-the-badge&logo=discord)](https://discord.gg/9hNxQdHYX)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-10B981?style=for-the-badge)](CONTRIBUTING.md)

**A modern open-source AI coding assistant running right inside your terminal.**
WindMist (`v2.0.0`) is built in high-performance Go to inspect code, edit files atomically across your workspace, and engage in multi-turn reasoning loops with local tools.
WindMist (`v2.0.1`) is built in high-performance Go to inspect code, edit files atomically across your workspace, and engage in multi-turn reasoning loops with local tools.

> **🌐 Official Website:** [windmist.vercel.app](https://windmist.vercel.app/) &nbsp;|&nbsp; **💻 Website Repo:** [`windmist-site`](https://github.com/Nithwin/windmist-site) &nbsp;|&nbsp; **💬 Community:** [Discord](https://discord.gg/9hNxQdHYX)

Expand All @@ -36,18 +36,24 @@ Experience an interactive AI pair programming session directly in your terminal:

## ⚙️ Installation

To install `windmist` (`v2.0.0`) using the Go toolchain (`Go 1.26+` required):
To install `windmist` (`v2.0.1`) using the Go toolchain (`Go 1.26+` required):

```bash
go install github.com/Nithwin/windmist/cmd/windmist@latest
go install github.com/Nithwin/WindMist@latest
```

Or clone and build directly from source:

```bash
git clone https://github.com/Nithwin/windmist.git
cd windmist
go build -o windmist ./cmd/windmist
git clone https://github.com/Nithwin/WindMist.git
cd WindMist
go build -o windmist .
```

Or use the universal installer (Linux/macOS):

```bash
curl -sSL https://raw.githubusercontent.com/Nithwin/WindMist/v2/scripts/install.sh | bash
```

---
Expand All @@ -71,7 +77,7 @@ go build -o windmist ./cmd/windmist

## ✨ Features & Capabilities

WindMist (`v2.0.0`) provides a robust, native engineering environment inside your terminal:
WindMist (`v2.0.1`) provides a robust, native engineering environment inside your terminal:

* ✅ **Interactive AI Chat & TUI:** Rich Bubble Tea and Lip Gloss interface with real-time streaming, markdown rendering, and syntax coloring.
* ✅ **Multi-Agent Orchestration:** Delegate background research to cheaper, faster Sub-agents while keeping your main context clean.
Expand All @@ -93,7 +99,7 @@ WindMist (`v2.0.0`) provides a robust, native engineering environment inside you
| `windmist chat <prompt>` | Run a single-turn or multi-turn agent instruction directly from the command line. |
| `windmist set <key> <val>` | Configure local environment and provider settings (`~/.windmist/config.yaml`). |
| `windmist show` | Display current local configuration settings. |
| `windmist version` | Print current semantic release build version (`v2.0.0`). |
| `windmist version` | Print current semantic release build version (`v2.0.1`). |

---

Expand Down
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ var rootCmd = &cobra.Command{

// Execute runs the root command.
func Execute() error {
rootCmd.Version = Version
return rootCmd.Execute()
}
42 changes: 38 additions & 4 deletions cmd/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
var uninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Uninstall the WindMist CLI executable and optionally purge configuration",
Long: `Safely removes the WindMist CLI executable from your system binary path and optionally cleans up saved configuration files (` + "`~/.config/windmist`" + `).`,
Long: `Safely removes the WindMist CLI executable from your system binary path and optionally cleans up saved configuration and data (` + "`~/.config/windmist`" + ` and ` + "`~/.windmist`" + `).`,
RunE: func(cmd *cobra.Command, args []string) error {
execPath, err := os.Executable()
if err != nil {
Expand All @@ -47,9 +47,34 @@ var uninstallCmd = &cobra.Command{

if !uninstallPurgeFlag && !uninstallYesFlag {
cfgDir, _ := config.ConfigDir()
if cfgDir != "" {
if _, err := os.Stat(cfgDir); err == nil {
fmt.Printf("❓ Also remove all configuration and chat history in %s? [y/N]: ", cfgDir)
home, _ := os.UserHomeDir()
dataDir := ""
if home != "" {
dataDir = filepath.Join(home, ".windmist")
}
purgeTarget := cfgDir
if dataDir != "" {
if purgeTarget != "" {
purgeTarget = purgeTarget + " and " + dataDir
} else {
purgeTarget = dataDir
}
}
if purgeTarget != "" {
cfgExists := false
if cfgDir != "" {
if _, err := os.Stat(cfgDir); err == nil {
cfgExists = true
}
}
dataExists := false
if dataDir != "" {
if _, err := os.Stat(dataDir); err == nil {
dataExists = true
}
}
if cfgExists || dataExists {
fmt.Printf("❓ Also remove all configuration and chat history in %s? [y/N]: ", purgeTarget)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if strings.EqualFold(input, "y") || strings.EqualFold(input, "yes") {
Expand Down Expand Up @@ -88,6 +113,15 @@ var uninstallCmd = &cobra.Command{
}
}
}
if home, err := os.UserHomeDir(); err == nil {
dataDir := filepath.Join(home, ".windmist")
if _, err := os.Stat(dataDir); err == nil {
fmt.Printf("🗑️ Removing data directory at %s...\n", dataDir)
if err := os.RemoveAll(dataDir); err != nil {
fmt.Printf("⚠️ Warning: failed to remove data directory: %v\n", err)
}
}
}
}

fmt.Println("\n✨ WindMist has been successfully uninstalled. Goodbye!")
Expand Down
2 changes: 1 addition & 1 deletion cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

var (
Version = "v2.0.0"
Version = "v2.0.1"
Commit = "none"
Date = "unknown"
)
Expand Down
21 changes: 21 additions & 0 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ func (a *Agent) Close() {
}
}

// Reconfigure updates session/mode/store settings without restarting MCP/LSP.
func (a *Agent) Reconfigure(cfg Config) {
if cfg.Mode == "" {
cfg.Mode = string(ModeBuild)
}
a.config.SessionID = cfg.SessionID
a.config.Mode = cfg.Mode
if cfg.Store != nil {
a.config.Store = cfg.Store
}
if cfg.MaxTurns > 0 {
a.config.MaxTurns = cfg.MaxTurns
}
if cfg.MaxContextTokens > 0 {
a.config.MaxContextTokens = cfg.MaxContextTokens
}
if cfg.Memory != nil {
a.config.Memory = cfg.Memory
}
}

// Manager returns the tools manager associated with the agent.
func (a *Agent) Manager() *tools.Manager {
return a.manager
Expand Down
5 changes: 3 additions & 2 deletions internal/agent/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (a *Agent) execute(ctx context.Context, calls []ai.ToolCall, onChunk func(s

// toolDefinitions converts the registered tool definitions from tools.Manager into ai.ToolDefinition format.
func (a *Agent) toolDefinitions(modeConfig ModeConfig) []ai.ToolDefinition {
if a.manager == nil {
if a.manager == nil || !modeConfig.AllowTools {
return nil
}

Expand All @@ -211,7 +211,8 @@ func (a *Agent) toolDefinitions(modeConfig ModeConfig) []ai.ToolDefinition {
})
}

if a.mcpManager != nil {
// MCP tools are write-capable; only attach them in build mode.
if a.mcpManager != nil && modeConfig.AllowFileEdits {
defs = append(defs, a.mcpManager.GetTools()...)
}

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

import (
"regexp"
"strings"
"unicode/utf8"
)

var (
greetingRE = regexp.MustCompile(`(?i)^(hi|hello|hey|yo|sup|hola|howdy|good\s+(morning|afternoon|evening)|thanks|thank\s*you|thx|ty|ok|okay|cool|great|nice|bye|goodbye|see\s+ya)[\s!.?]*$`)
buildRE = regexp.MustCompile(`(?i)\b(implement|fix|create|add|write|edit|modify|update|refactor|rename|delete|remove|install|migrate|generate|scaffold|build|make|patch|apply|replace|insert|debug|resolve)\b`)
planRE = regexp.MustCompile(`(?i)\b(plan|analyze|analyse|review|explain|search|find|locate|how\s+(do|does|to)|what\s+(is|are|does)|why\s+(is|are|does)|where\s+(is|are)|compare|outline|architect|design\s+a|walk\s*me\s*through|summarize|summarise)\b`)
codeHintRE = regexp.MustCompile(`(?i)(\x60{3}|@[\w./\\-]+|\.(go|ts|tsx|js|jsx|py|rs|java|kt|c|cpp|h|cs|rb|php|swift)\b|func\s+\w+|class\s+\w+|def\s+\w+)`)
)

// ClassifyIntent resolves auto-mode without an LLM call when confidence is high.
// Returns (mode, ok). When ok is false, the caller should use the LLM router.
func ClassifyIntent(userPrompt string) (Mode, bool) {
p := strings.TrimSpace(userPrompt)
if p == "" {
return ModeChat, true
}

lower := strings.ToLower(p)
runes := utf8.RuneCountInString(p)

// Pure greetings / acknowledgements — never need plan or build.
if greetingRE.MatchString(strings.TrimSpace(lower)) {
return ModeChat, true
}

// Very short small-talk without code signals.
if runes <= 40 && !codeHintRE.MatchString(p) && !buildRE.MatchString(lower) {
// "hi there", "how's it going", "who are you", etc.
if !planRE.MatchString(lower) || runes <= 20 {
return ModeChat, true
}
}

hasBuild := buildRE.MatchString(lower)
hasPlan := planRE.MatchString(lower)
hasCode := codeHintRE.MatchString(p)

// Explicit implementation intent.
if hasBuild && (hasCode || runes > 25) {
return ModeBuild, true
}
if hasBuild && !hasPlan {
return ModeBuild, true
}

// Analysis / explanation without edit verbs.
if hasPlan && !hasBuild {
// Short conceptual questions stay in chat (no tools/repo map tax).
if runes < 100 && !hasCode && !strings.Contains(lower, "codebase") &&
!strings.Contains(lower, "this project") && !strings.Contains(lower, "this repo") {
return ModeChat, true
}
return ModePlan, true
}

// Short questions with no engineering verbs → chat.
if runes < 60 && !hasBuild && !hasCode {
return ModeChat, true
}

return "", false
}

// IsTrivialPrompt reports whether a message is too trivial to spend an
// auto-title API call on (greetings, thanks, etc.).
func IsTrivialPrompt(userPrompt string) bool {
mode, ok := ClassifyIntent(userPrompt)
return ok && mode == ModeChat && utf8.RuneCountInString(strings.TrimSpace(userPrompt)) <= 40
}
Loading
Loading