diff --git a/MODIFICATIONS.md b/MODIFICATIONS.md new file mode 100644 index 0000000000..f75536ba64 --- /dev/null +++ b/MODIFICATIONS.md @@ -0,0 +1,78 @@ +# Wove — Modifications from Wave Terminal + +Wove is built on the [Wave Terminal](https://github.com/wavetermdev/waveterm) engine (Apache 2.0). +This document lists all modifications and additions made in Wove. + +## MCP (Model Context Protocol) Integration +- Full MCP client package (`pkg/mcpclient/`) — JSON-RPC 2.0 over stdio +- Auto-detect `.mcp.json` in terminal CWD with connect banner +- MCP tools registered as AI tools — model queries database, searches docs, reads logs +- MCP auto-context injection (database schema, application info) +- MCP Client widget in sidebar with tools list, call log, and Run button +- MCP Context toggle in AI panel header + +## AI Planning System +- Multi-step execution plans with `wave_utils(action='plan_create')` +- Auto-append steps: lint, review against project conventions, write tests, run tests +- Live progress panel with expandable step results +- Plans persist to disk, survive restarts +- Detailed plan steps with file paths, conventions, and acceptance criteria + +## Project Intelligence +- Reads WAVE.md, CLAUDE.md, .cursorrules, AGENTS.md automatically +- Project stack injection (tech stack in every request) +- Critical rules auto-extraction (must/always/never patterns) +- Project tree on first message (directory structure) +- Two-step project_instructions tool (table of contents → specific sections) +- Smart filtering by technology (PHP sections for .php files, etc.) + +## Web Content Tools +- `web_read_text` — extract clean text by CSS selector +- `web_read_html` — extract innerHTML by CSS selector +- `web_seo_audit` — full SEO audit (JSON-LD, OG, meta, headings, alt text, links) +- `execJs` option for arbitrary JavaScript execution in webview +- Auto-refresh page before reading content +- AI Reading highlight animation on matched elements +- Page title tracking in block metadata + +## Session History +- Chat history saved per tab at shutdown +- Previous Session banner in AI panel +- `session_history` tool for AI to read previous work +- Chat-to-tab mapping registry + +## Auto-approve File Reading +- Session-level auto-approve for directories +- Sensitive path protection (~/.ssh, ~/.aws, .env) +- Symlink bypass prevention via canonical path resolution + +## AI Model Management +- Quick Add Model menu (Claude, GPT, Gemini, MiniMax, Ollama, OpenRouter) +- Inline API key input with secure storage +- 10 built-in BYOK presets with endpoints +- Secret-based preset filtering (hide unconfigured models) +- Ollama connectivity check + +## System Prompt Optimization +- "Senior software engineer" role for better code quality +- "Read sibling files before writing" pattern matching +- Self-review after each plan step +- Compressed tool descriptions (~60% fewer tokens) +- Consolidated wave_utils multi-action tool +- English-only code comments enforcement +- Terminal commands reference (grep, find, php -l, pint) + +## Quality & Reliability +- Syntax highlighting fix in AI diff viewer (preserved file extensions) +- Language detection from filename (30+ extensions) +- New file diff: empty original, green additions +- Web page title in tab state (catches 500 errors) +- Default AI timeout: 90 seconds (was infinite) +- Default max output tokens: 16K (was 4K) +- Friendly error messages with Retry button +- MCP client: mutex protection, read timeout, graceful shutdown +- RPC handler input validation for WebSelector opts + +## Based On +- [Wave Terminal](https://github.com/wavetermdev/waveterm) by Command Line Inc. +- Licensed under Apache License 2.0 diff --git a/NOTICE b/NOTICE index b592fcd9b4..b1bdd9106e 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1,7 @@ -Copyright 2025, Command Line Inc. +Wove +Copyright 2025-2026 MITS Sp. z o.o. + +Built on Wave Terminal +Copyright 2025 Command Line Inc. +Licensed under Apache License 2.0 +https://github.com/wavetermdev/waveterm diff --git a/README.md b/README.md index 2b8e0637ae..2d792e1854 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,153 @@ -

- - - - - Wave Terminal Logo - - -
-

+# Wove -# Wave Terminal +**The AI-first terminal for developers who code, not click.** -
+Built on the [Wave Terminal](https://github.com/wavetermdev/waveterm) engine. Wove adds deep project intelligence, MCP integration, execution planning, and multi-model support — turning your terminal into an AI development environment. -[English](README.md) | [한국어](README.ko.md) +## Why Wove? -
- -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm?ref=badge_shield) - -Wave is an open-source, AI-integrated terminal for macOS, Linux, and Windows. It works with any AI model. Bring your own API keys for OpenAI, Claude, or Gemini, or run local models via Ollama and LM Studio. No accounts required. - -Wave also supports durable SSH sessions that survive network interruptions and restarts, with automatic reconnection. Edit remote files with a built-in graphical editor and preview files inline without leaving the terminal. - -![WaveTerm Screenshot](./assets/wave-screenshot.webp) +| Feature | Standard terminals | Warp | Wove | +|---|---|---|---| +| AI chat in terminal | Some | Yes | Yes | +| AI reads your database schema | No | No | **Yes (MCP)** | +| AI reads project conventions | No | Partial | **Yes (CLAUDE.md, WAVE.md)** | +| AI creates execution plans | No | Basic | **Yes (with auto-tests, lint, review)** | +| SEO audit tool | No | No | **Yes** | +| Session history across restarts | No | No | **Yes** | +| Web page content reading | No | No | **Yes (text, HTML, JS)** | +| Multi-model BYOK | Limited | Limited | **10 presets (3 clicks to add)** | +| Open source | Some | No | **Yes (Apache 2.0)** | ## Key Features -- Wave AI - Context-aware terminal assistant that reads your terminal output, analyzes widgets, and performs file operations -- Durable SSH Sessions - Remote terminal sessions survive connection interruptions, network changes, and Wave restarts with automatic reconnection -- Flexible drag & drop interface to organize terminal blocks, editors, web browsers, and AI assistants -- Built-in editor for editing remote files with syntax highlighting and modern editor features -- Rich file preview system for remote files (markdown, images, video, PDFs, CSVs, directories) -- Quick full-screen toggle for any block - expand terminals, editors, and previews for better visibility, then instantly return to multi-block view -- AI chat widget with support for multiple models (OpenAI, Claude, Azure, Perplexity, Ollama) -- Command Blocks for isolating and monitoring individual commands -- One-click remote connections with full terminal and file system access -- Secure secret storage using native system backends - store API keys and credentials locally, access them across SSH sessions -- Rich customization including tab themes, terminal styles, and background images -- Powerful `wsh` command system for managing your workspace from the CLI and sharing data between terminal sessions -- Connected file management with `wsh file` - seamlessly copy and sync files between local and remote SSH hosts - -## Wave AI - -Wave AI is your context-aware terminal assistant with access to your workspace: - -- **Terminal Context**: Reads terminal output and scrollback for debugging and analysis -- **File Operations**: Read, write, and edit files with automatic backups and user approval -- **CLI Integration**: Use `wsh ai` to pipe output or attach files directly from the command line -- **BYOK Support**: Bring your own API keys for OpenAI, Claude, Gemini, Azure, and other providers -- **Local Models**: Run local models with Ollama, LM Studio, and other OpenAI-compatible providers -- **Free Beta**: Included AI credits while we refine the experience -- **Coming Soon**: Command execution (with approval) - -Learn more in our [Wave AI documentation](https://docs.waveterm.dev/waveai) and [Wave AI Modes documentation](https://docs.waveterm.dev/waveai-modes). +### MCP Integration +Connect to any [Model Context Protocol](https://modelcontextprotocol.io/) server. Wove auto-detects `.mcp.json` in your project and gives AI direct access to your database, documentation, and logs. + +```json +{ + "mcpServers": { + "my-server": { + "command": "node", + "args": ["mcp-server.js"] + } + } +} +``` + +AI automatically queries your database schema before writing SQL, checks framework docs before suggesting patterns, and reads error logs before debugging. + +### AI Planning System +For code tasks, Wove creates detailed execution plans with: +- Concrete file paths and pattern references +- Auto-appended steps: lint, review against project conventions, write tests, run tests +- Live progress panel in the AI sidebar +- Plans survive restarts — pick up where you left off + +### Project Intelligence +Wove reads your project's coding conventions (WAVE.md, CLAUDE.md, .cursorrules) and enforces them: +- Tech stack injected into every request (AI knows it's Inertia, not axios) +- Critical rules auto-extracted and always present +- Project structure on first message +- Smart section filtering by technology + +### Web Content Tools +AI can navigate, read, and audit web pages: +- **web_read_text** — clean text by CSS selector +- **web_read_html** — raw HTML for structure inspection +- **web_seo_audit** — JSON-LD, Open Graph, meta tags, headings, alt text, links +- Visual highlight animation when AI reads page elements + +### Multi-Model Support (BYOK) +Bring your own API keys. Quick Add in 3 clicks: + +| Provider | Models | +|---|---| +| Anthropic | Claude Sonnet 4.6, Opus 4.6 | +| OpenAI | GPT-5 Mini, GPT-5.1 | +| Google | Gemini 3.0 Flash, Pro | +| MiniMax | M2.7 | +| Ollama | Any local model | +| OpenRouter | Any model | + +### Session History +AI remembers what you did in previous sessions. Chat history persists per tab, with a visual banner showing previous work. + +### Auto-approve File Reading +Approve a directory once — AI reads files without asking each time. Sensitive paths (~/.ssh, ~/.aws, .env) are never auto-approved. ## Installation -Wave Terminal works on macOS, Linux, and Windows. - -Platform-specific installation instructions can be found [here](https://docs.waveterm.dev/gettingstarted). - -You can also install Wave Terminal directly from: [www.waveterm.dev/download](https://www.waveterm.dev/download). - -### Minimum requirements - -Wave Terminal runs on the following platforms: - -- macOS 11 or later (arm64, x64) -- Windows 10 1809 or later (x64) -- Linux based on glibc-2.28 or later (Debian 10, RHEL 8, Ubuntu 20.04, etc.) (arm64, x64) - -The WSH helper runs on the following platforms: - -- macOS 11 or later (arm64, x64) -- Windows 10 or later (x64) -- Linux Kernel 2.6.32 or later (x64), Linux Kernel 3.1 or later (arm64) - -## Roadmap - -Wave is constantly improving! Our roadmap will be continuously updated with our goals for each release. You can find it [here](./ROADMAP.md). - -Want to provide input to our future releases? Connect with us on [Discord](https://discord.gg/XfvZ334gwU) or open a [Feature Request](https://github.com/wavetermdev/waveterm/issues/new/choose)! - -## Links - -- Homepage — https://www.waveterm.dev -- Download Page — https://www.waveterm.dev/download -- Documentation — https://docs.waveterm.dev -- X — https://x.com/wavetermdev -- Discord Community — https://discord.gg/XfvZ334gwU - -## Building from Source - -See [Building Wave Terminal](BUILD.md). +Wove works on macOS, Linux, and Windows. + +### Build from source + +```bash +git clone https://github.com/woveterm/wove.git +cd wove +task init +task dev +``` + +### Requirements +- macOS 11+, Windows 10 1809+, or Linux (glibc-2.28+) +- Node.js 22 LTS +- Go 1.25+ + +## Configuration + +### AI Modes +Configure in `~/.config/woveterm/waveai.json`: +```json +{ + "my-model": { + "display:name": "My Model", + "ai:apitype": "anthropic-messages", + "ai:model": "claude-sonnet-4-6", + "ai:endpoint": "https://api.anthropic.com/v1/messages", + "ai:apitokensecretname": "my_api_key", + "ai:capabilities": ["tools", "images", "pdfs"] + } +} +``` + +### Project Instructions +Create `WAVE.md` in your project root: +```markdown +## Project +My App — Laravel 11, Inertia.js, Vue 3 + +## Conventions +- Always use Form Request classes for validation +- Use Eloquent scopes, not raw queries +- Run vendor/bin/pint after changes +``` + +## How It Works + +``` +User message + | + v +[Project Stack] -> AI knows: "Laravel + Inertia + Vue" +[Critical Rules] -> AI knows: "must write tests, must use PHPDoc" +[Project Tree] -> AI knows: file structure +[MCP Context] -> AI knows: database schema, app info +[Active Plan] -> AI knows: what step to execute next + | + v +AI creates plan -> reads sibling files -> writes code -> reviews -> tests -> lint +``` + +## Built On + +Wove is built on [Wave Terminal](https://github.com/wavetermdev/waveterm) by [Command Line Inc.](https://www.commandline.dev/), licensed under Apache License 2.0. + +See [MODIFICATIONS.md](MODIFICATIONS.md) for a complete list of changes from upstream. ## Contributing -Wave uses GitHub Issues for issue tracking. - -Find more information in our [Contributions Guide](CONTRIBUTING.md), which includes: - -- [Ways to contribute](CONTRIBUTING.md#contributing-to-wave-terminal) -- [Contribution guidelines](CONTRIBUTING.md#before-you-start) - -### Sponsoring Wave ❤️ - -If Wave Terminal is useful to you or your company, consider sponsoring development. - -Sponsorship helps support the time spent building and maintaining the project. - -- https://github.com/sponsors/wavetermdev +Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). ## License -Wave Terminal is licensed under the Apache-2.0 License. For more information on our dependencies, see [here](./ACKNOWLEDGEMENTS.md). +Apache License 2.0. See [LICENSE](LICENSE). diff --git a/cmd/generatego/main-generatego.go b/cmd/generatego/main-generatego.go index ab7e338439..bd1a7701c1 100644 --- a/cmd/generatego/main-generatego.go +++ b/cmd/generatego/main-generatego.go @@ -9,11 +9,11 @@ import ( "reflect" "strings" - "github.com/wavetermdev/waveterm/pkg/gogen" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" - "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/woveterm/wove/pkg/gogen" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wconfig" + "github.com/woveterm/wove/pkg/wshrpc" ) const WshClientFileName = "pkg/wshrpc/wshclient/wshclient.go" @@ -24,15 +24,15 @@ func GenerateWshClient() error { fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", WshClientFileName) var buf strings.Builder gogen.GenerateBoilerplate(&buf, "wshclient", []string{ - "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes", - "github.com/wavetermdev/waveterm/pkg/baseds", - "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata", - "github.com/wavetermdev/waveterm/pkg/vdom", - "github.com/wavetermdev/waveterm/pkg/waveobj", - "github.com/wavetermdev/waveterm/pkg/wconfig", - "github.com/wavetermdev/waveterm/pkg/wps", - "github.com/wavetermdev/waveterm/pkg/wshrpc", - "github.com/wavetermdev/waveterm/pkg/wshutil", + "github.com/woveterm/wove/pkg/aiusechat/uctypes", + "github.com/woveterm/wove/pkg/baseds", + "github.com/woveterm/wove/pkg/telemetry/telemetrydata", + "github.com/woveterm/wove/pkg/vdom", + "github.com/woveterm/wove/pkg/waveobj", + "github.com/woveterm/wove/pkg/wconfig", + "github.com/woveterm/wove/pkg/wps", + "github.com/woveterm/wove/pkg/wshrpc", + "github.com/woveterm/wove/pkg/wshutil", }) wshDeclMap := wshrpc.GenerateWshCommandDeclMap() for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) { diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go index 2b14a95781..8a3b5781fb 100644 --- a/cmd/generateschema/main-generateschema.go +++ b/cmd/generateschema/main-generateschema.go @@ -11,9 +11,9 @@ import ( "reflect" "github.com/invopop/jsonschema" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wconfig" ) const WaveSchemaSettingsFileName = "schema/settings.json" diff --git a/cmd/generatets/main-generatets.go b/cmd/generatets/main-generatets.go index f282f9fa19..7e6e01b159 100644 --- a/cmd/generatets/main-generatets.go +++ b/cmd/generatets/main-generatets.go @@ -11,10 +11,10 @@ import ( "sort" "strings" - "github.com/wavetermdev/waveterm/pkg/service" - "github.com/wavetermdev/waveterm/pkg/tsgen" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/woveterm/wove/pkg/service" + "github.com/woveterm/wove/pkg/tsgen" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/wshrpc" ) func generateTypesFile(tsTypesMap map[reflect.Type]string) error { diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index 70c8b3a005..7ce4f34b91 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -14,38 +14,40 @@ import ( "time" "github.com/joho/godotenv" - "github.com/wavetermdev/waveterm/pkg/aiusechat" - "github.com/wavetermdev/waveterm/pkg/authkey" - "github.com/wavetermdev/waveterm/pkg/blockcontroller" - "github.com/wavetermdev/waveterm/pkg/blocklogger" - "github.com/wavetermdev/waveterm/pkg/filebackup" - "github.com/wavetermdev/waveterm/pkg/filestore" - "github.com/wavetermdev/waveterm/pkg/jobcontroller" - "github.com/wavetermdev/waveterm/pkg/panichandler" - "github.com/wavetermdev/waveterm/pkg/remote/conncontroller" - "github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs" - "github.com/wavetermdev/waveterm/pkg/secretstore" - "github.com/wavetermdev/waveterm/pkg/service" - "github.com/wavetermdev/waveterm/pkg/telemetry" - "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata" - "github.com/wavetermdev/waveterm/pkg/util/envutil" - "github.com/wavetermdev/waveterm/pkg/util/shellutil" - "github.com/wavetermdev/waveterm/pkg/util/sigutil" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wcloud" - "github.com/wavetermdev/waveterm/pkg/wconfig" - "github.com/wavetermdev/waveterm/pkg/wcore" - "github.com/wavetermdev/waveterm/pkg/web" - "github.com/wavetermdev/waveterm/pkg/wps" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshremote" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver" - "github.com/wavetermdev/waveterm/pkg/wshutil" - "github.com/wavetermdev/waveterm/pkg/wslconn" - "github.com/wavetermdev/waveterm/pkg/wstore" + "github.com/woveterm/wove/pkg/aiusechat" + "github.com/woveterm/wove/pkg/authkey" + "github.com/woveterm/wove/pkg/blockcontroller" + "github.com/woveterm/wove/pkg/blocklogger" + "github.com/woveterm/wove/pkg/filebackup" + "github.com/woveterm/wove/pkg/filestore" + "github.com/woveterm/wove/pkg/jobcontroller" + "github.com/woveterm/wove/pkg/panichandler" + "github.com/woveterm/wove/pkg/remote/conncontroller" + "github.com/woveterm/wove/pkg/remote/fileshare/wshfs" + "github.com/woveterm/wove/pkg/secretstore" + "github.com/woveterm/wove/pkg/service" + "github.com/woveterm/wove/pkg/telemetry" + "github.com/woveterm/wove/pkg/telemetry/telemetrydata" + "github.com/woveterm/wove/pkg/util/envutil" + "github.com/woveterm/wove/pkg/util/shellutil" + "github.com/woveterm/wove/pkg/util/sigutil" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/aiusechat/sessionhistory" + "github.com/woveterm/wove/pkg/mcpclient" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wcloud" + "github.com/woveterm/wove/pkg/wconfig" + "github.com/woveterm/wove/pkg/wcore" + "github.com/woveterm/wove/pkg/web" + "github.com/woveterm/wove/pkg/wps" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc/wshremote" + "github.com/woveterm/wove/pkg/wshrpc/wshserver" + "github.com/woveterm/wove/pkg/wshutil" + "github.com/woveterm/wove/pkg/wslconn" + "github.com/woveterm/wove/pkg/wstore" "net/http" _ "net/http/pprof" @@ -81,6 +83,8 @@ func doShutdown(reason string) { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() go blockcontroller.StopAllBlockControllersForShutdown() + sessionhistory.SaveAll() + mcpclient.GetManager().Shutdown() shutdownActivityUpdate() sendTelemetryWrapper() // TODO deal with flush in progress diff --git a/cmd/test-conn/cliprovider.go b/cmd/test-conn/cliprovider.go index 661c40544a..1826f1a8f1 100644 --- a/cmd/test-conn/cliprovider.go +++ b/cmd/test-conn/cliprovider.go @@ -10,7 +10,7 @@ import ( "os" "strings" - "github.com/wavetermdev/waveterm/pkg/userinput" + "github.com/woveterm/wove/pkg/userinput" ) type CLIProvider struct { diff --git a/cmd/test-conn/testutil.go b/cmd/test-conn/testutil.go index f82e7b7195..0915552846 100644 --- a/cmd/test-conn/testutil.go +++ b/cmd/test-conn/testutil.go @@ -13,18 +13,18 @@ import ( "time" "github.com/google/uuid" - "github.com/wavetermdev/waveterm/pkg/remote" - "github.com/wavetermdev/waveterm/pkg/remote/conncontroller" - "github.com/wavetermdev/waveterm/pkg/shellexec" - "github.com/wavetermdev/waveterm/pkg/userinput" - "github.com/wavetermdev/waveterm/pkg/util/shellutil" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/wavejwt" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver" - "github.com/wavetermdev/waveterm/pkg/wshutil" - "github.com/wavetermdev/waveterm/pkg/wstore" + "github.com/woveterm/wove/pkg/remote" + "github.com/woveterm/wove/pkg/remote/conncontroller" + "github.com/woveterm/wove/pkg/shellexec" + "github.com/woveterm/wove/pkg/userinput" + "github.com/woveterm/wove/pkg/util/shellutil" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/wavejwt" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wconfig" + "github.com/woveterm/wove/pkg/wshrpc/wshserver" + "github.com/woveterm/wove/pkg/wshutil" + "github.com/woveterm/wove/pkg/wstore" ) func setupWaveEnvVars() error { diff --git a/cmd/test-streammanager/bridge.go b/cmd/test-streammanager/bridge.go index 501adc3d32..5c607fbe0f 100644 --- a/cmd/test-streammanager/bridge.go +++ b/cmd/test-streammanager/bridge.go @@ -6,7 +6,7 @@ package main import ( "fmt" - "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc" ) // WriterBridge - used by the writer broker diff --git a/cmd/test-streammanager/deliverypipe.go b/cmd/test-streammanager/deliverypipe.go index 8f8451f45a..34f1a9f828 100644 --- a/cmd/test-streammanager/deliverypipe.go +++ b/cmd/test-streammanager/deliverypipe.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc" ) type DeliveryConfig struct { diff --git a/cmd/test-streammanager/main-test-streammanager.go b/cmd/test-streammanager/main-test-streammanager.go index 4e6702e790..5eff57e601 100644 --- a/cmd/test-streammanager/main-test-streammanager.go +++ b/cmd/test-streammanager/main-test-streammanager.go @@ -11,9 +11,9 @@ import ( "time" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/jobmanager" - "github.com/wavetermdev/waveterm/pkg/streamclient" - "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/woveterm/wove/pkg/jobmanager" + "github.com/woveterm/wove/pkg/streamclient" + "github.com/woveterm/wove/pkg/wshrpc" ) type TestConfig struct { diff --git a/cmd/testai/main-testai.go b/cmd/testai/main-testai.go index 606e6ac6a1..39ecfeeb95 100644 --- a/cmd/testai/main-testai.go +++ b/cmd/testai/main-testai.go @@ -15,9 +15,9 @@ import ( "time" "github.com/google/uuid" - "github.com/wavetermdev/waveterm/pkg/aiusechat" - "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" - "github.com/wavetermdev/waveterm/pkg/web/sse" + "github.com/woveterm/wove/pkg/aiusechat" + "github.com/woveterm/wove/pkg/aiusechat/uctypes" + "github.com/woveterm/wove/pkg/web/sse" ) //go:embed testschema.json diff --git a/cmd/testopenai/main-testopenai.go b/cmd/testopenai/main-testopenai.go index 7017407b47..eaea6bd88a 100644 --- a/cmd/testopenai/main-testopenai.go +++ b/cmd/testopenai/main-testopenai.go @@ -15,8 +15,8 @@ import ( "os" "time" - "github.com/wavetermdev/waveterm/pkg/aiusechat" - "github.com/wavetermdev/waveterm/pkg/aiusechat/openai" + "github.com/woveterm/wove/pkg/aiusechat" + "github.com/woveterm/wove/pkg/aiusechat/openai" ) func makeOpenAIRequest(ctx context.Context, apiKey, model, message string, tools bool) error { diff --git a/cmd/testsummarize/main-testsummarize.go b/cmd/testsummarize/main-testsummarize.go index fc16e59e04..587431fc68 100644 --- a/cmd/testsummarize/main-testsummarize.go +++ b/cmd/testsummarize/main-testsummarize.go @@ -10,7 +10,7 @@ import ( "os" "time" - "github.com/wavetermdev/waveterm/pkg/aiusechat/google" + "github.com/woveterm/wove/pkg/aiusechat/google" ) func printUsage() { diff --git a/cmd/wsh/cmd/wshcmd-ai.go b/cmd/wsh/cmd/wshcmd-ai.go index 643c80ee7a..d43ec61eda 100644 --- a/cmd/wsh/cmd/wshcmd-ai.go +++ b/cmd/wsh/cmd/wshcmd-ai.go @@ -14,11 +14,11 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/fileutil" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/util/fileutil" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var aiCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-badge.go b/cmd/wsh/cmd/wshcmd-badge.go index 590ed1e40b..04b92dfd3d 100644 --- a/cmd/wsh/cmd/wshcmd-badge.go +++ b/cmd/wsh/cmd/wshcmd-badge.go @@ -9,12 +9,12 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/baseds" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wps" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/baseds" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wps" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var badgeCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-blocks.go b/cmd/wsh/cmd/wshcmd-blocks.go index 7e4b935ee3..683eb99541 100644 --- a/cmd/wsh/cmd/wshcmd-blocks.go +++ b/cmd/wsh/cmd/wshcmd-blocks.go @@ -11,9 +11,9 @@ import ( "text/tabwriter" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) // Command-line flags for the blocks commands diff --git a/cmd/wsh/cmd/wshcmd-conn.go b/cmd/wsh/cmd/wshcmd-conn.go index b0a6cd3522..8128167693 100644 --- a/cmd/wsh/cmd/wshcmd-conn.go +++ b/cmd/wsh/cmd/wshcmd-conn.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/remote" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/remote" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var connCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-connserver.go b/cmd/wsh/cmd/wshcmd-connserver.go index 8fde91dc3c..7785cf3ba3 100644 --- a/cmd/wsh/cmd/wshcmd-connserver.go +++ b/cmd/wsh/cmd/wshcmd-connserver.go @@ -16,18 +16,18 @@ import ( "time" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/baseds" - "github.com/wavetermdev/waveterm/pkg/panichandler" - "github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs" - "github.com/wavetermdev/waveterm/pkg/util/envutil" - "github.com/wavetermdev/waveterm/pkg/util/packetparser" - "github.com/wavetermdev/waveterm/pkg/util/sigutil" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/wavejwt" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshremote" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/baseds" + "github.com/woveterm/wove/pkg/panichandler" + "github.com/woveterm/wove/pkg/remote/fileshare/wshfs" + "github.com/woveterm/wove/pkg/util/envutil" + "github.com/woveterm/wove/pkg/util/packetparser" + "github.com/woveterm/wove/pkg/util/sigutil" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/wavejwt" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc/wshremote" + "github.com/woveterm/wove/pkg/wshutil" ) var serverCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-createblock.go b/cmd/wsh/cmd/wshcmd-createblock.go index aaf153e232..22a636f945 100644 --- a/cmd/wsh/cmd/wshcmd-createblock.go +++ b/cmd/wsh/cmd/wshcmd-createblock.go @@ -7,9 +7,9 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var createBlockMagnified bool diff --git a/cmd/wsh/cmd/wshcmd-debug.go b/cmd/wsh/cmd/wshcmd-debug.go index 9efac0ff87..18e876d986 100644 --- a/cmd/wsh/cmd/wshcmd-debug.go +++ b/cmd/wsh/cmd/wshcmd-debug.go @@ -7,7 +7,7 @@ import ( "encoding/json" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var debugCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-debugterm.go b/cmd/wsh/cmd/wshcmd-debugterm.go index 66346c460a..296580e646 100644 --- a/cmd/wsh/cmd/wshcmd-debugterm.go +++ b/cmd/wsh/cmd/wshcmd-debugterm.go @@ -15,8 +15,8 @@ import ( "unicode/utf8" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) const ( diff --git a/cmd/wsh/cmd/wshcmd-deleteblock.go b/cmd/wsh/cmd/wshcmd-deleteblock.go index 76518e721c..48d1ba5c40 100644 --- a/cmd/wsh/cmd/wshcmd-deleteblock.go +++ b/cmd/wsh/cmd/wshcmd-deleteblock.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var deleteBlockCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-editconfig.go b/cmd/wsh/cmd/wshcmd-editconfig.go index cbd4015bae..800b5aecb0 100644 --- a/cmd/wsh/cmd/wshcmd-editconfig.go +++ b/cmd/wsh/cmd/wshcmd-editconfig.go @@ -7,9 +7,9 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var editConfigMagnified bool diff --git a/cmd/wsh/cmd/wshcmd-editor.go b/cmd/wsh/cmd/wshcmd-editor.go index 4968b17509..c8a1266527 100644 --- a/cmd/wsh/cmd/wshcmd-editor.go +++ b/cmd/wsh/cmd/wshcmd-editor.go @@ -10,10 +10,10 @@ import ( "path/filepath" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wps" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wps" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var editMagnified bool diff --git a/cmd/wsh/cmd/wshcmd-file-util.go b/cmd/wsh/cmd/wshcmd-file-util.go index 13ef433ec5..88c2528755 100644 --- a/cmd/wsh/cmd/wshcmd-file-util.go +++ b/cmd/wsh/cmd/wshcmd-file-util.go @@ -11,11 +11,11 @@ import ( "io/fs" "strings" - "github.com/wavetermdev/waveterm/pkg/remote/connparse" - "github.com/wavetermdev/waveterm/pkg/remote/fileshare/fsutil" - "github.com/wavetermdev/waveterm/pkg/util/fileutil" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/remote/connparse" + "github.com/woveterm/wove/pkg/remote/fileshare/fsutil" + "github.com/woveterm/wove/pkg/util/fileutil" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) func convertNotFoundErr(err error) error { diff --git a/cmd/wsh/cmd/wshcmd-file.go b/cmd/wsh/cmd/wshcmd-file.go index 3e2cc5721b..549e25195c 100644 --- a/cmd/wsh/cmd/wshcmd-file.go +++ b/cmd/wsh/cmd/wshcmd-file.go @@ -16,9 +16,9 @@ import ( "time" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/utilfn" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/util/utilfn" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" "golang.org/x/term" ) diff --git a/cmd/wsh/cmd/wshcmd-focusblock.go b/cmd/wsh/cmd/wshcmd-focusblock.go index 3f6603a3e2..fb3521bc38 100644 --- a/cmd/wsh/cmd/wshcmd-focusblock.go +++ b/cmd/wsh/cmd/wshcmd-focusblock.go @@ -8,8 +8,8 @@ import ( "os" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var focusBlockCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-getmeta.go b/cmd/wsh/cmd/wshcmd-getmeta.go index f5e1e40f67..1d15682587 100644 --- a/cmd/wsh/cmd/wshcmd-getmeta.go +++ b/cmd/wsh/cmd/wshcmd-getmeta.go @@ -10,8 +10,8 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var getMetaCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-getvar.go b/cmd/wsh/cmd/wshcmd-getvar.go index 9391c4f5f2..ba6b3dc599 100644 --- a/cmd/wsh/cmd/wshcmd-getvar.go +++ b/cmd/wsh/cmd/wshcmd-getvar.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var getVarCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-jobdebug.go b/cmd/wsh/cmd/wshcmd-jobdebug.go index 27a7b74772..e5b01ec81a 100644 --- a/cmd/wsh/cmd/wshcmd-jobdebug.go +++ b/cmd/wsh/cmd/wshcmd-jobdebug.go @@ -10,9 +10,9 @@ import ( "os" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var jobDebugCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-jobmanager.go b/cmd/wsh/cmd/wshcmd-jobmanager.go index bf5562c3a7..7a8ce02d69 100644 --- a/cmd/wsh/cmd/wshcmd-jobmanager.go +++ b/cmd/wsh/cmd/wshcmd-jobmanager.go @@ -14,7 +14,7 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/jobmanager" + "github.com/woveterm/wove/pkg/jobmanager" ) var jobManagerCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-launch.go b/cmd/wsh/cmd/wshcmd-launch.go index 3ec582a6cd..896bdc3f5c 100644 --- a/cmd/wsh/cmd/wshcmd-launch.go +++ b/cmd/wsh/cmd/wshcmd-launch.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var magnifyBlock bool diff --git a/cmd/wsh/cmd/wshcmd-notify.go b/cmd/wsh/cmd/wshcmd-notify.go index de2086e1f7..e4fb04a481 100644 --- a/cmd/wsh/cmd/wshcmd-notify.go +++ b/cmd/wsh/cmd/wshcmd-notify.go @@ -7,9 +7,9 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var notifyTitle string diff --git a/cmd/wsh/cmd/wshcmd-rcfiles.go b/cmd/wsh/cmd/wshcmd-rcfiles.go index 745d325682..8f0130a9ee 100644 --- a/cmd/wsh/cmd/wshcmd-rcfiles.go +++ b/cmd/wsh/cmd/wshcmd-rcfiles.go @@ -5,7 +5,7 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/wshutil" ) func init() { diff --git a/cmd/wsh/cmd/wshcmd-readfile.go b/cmd/wsh/cmd/wshcmd-readfile.go index 09344967de..8e67985cf6 100644 --- a/cmd/wsh/cmd/wshcmd-readfile.go +++ b/cmd/wsh/cmd/wshcmd-readfile.go @@ -8,9 +8,9 @@ import ( "os" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var readFileCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-root.go b/cmd/wsh/cmd/wshcmd-root.go index 534ce0c31a..03579f2c8e 100644 --- a/cmd/wsh/cmd/wshcmd-root.go +++ b/cmd/wsh/cmd/wshcmd-root.go @@ -10,11 +10,11 @@ import ( "runtime/debug" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/shellutil" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/util/shellutil" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var ( diff --git a/cmd/wsh/cmd/wshcmd-run.go b/cmd/wsh/cmd/wshcmd-run.go index 6faf424c99..a339dd922b 100644 --- a/cmd/wsh/cmd/wshcmd-run.go +++ b/cmd/wsh/cmd/wshcmd-run.go @@ -10,11 +10,11 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/envutil" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/util/envutil" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var runCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-secret.go b/cmd/wsh/cmd/wshcmd-secret.go index 916e3ae4a5..11a1dbb7b6 100644 --- a/cmd/wsh/cmd/wshcmd-secret.go +++ b/cmd/wsh/cmd/wshcmd-secret.go @@ -9,9 +9,9 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) // secretNameRegex must match the validation in pkg/wconfig/secretstore.go diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go index fb5cf0fec0..b90ab474de 100644 --- a/cmd/wsh/cmd/wshcmd-setbg.go +++ b/cmd/wsh/cmd/wshcmd-setbg.go @@ -12,10 +12,10 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/fileutil" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/util/fileutil" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var setBgCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-setconfig.go b/cmd/wsh/cmd/wshcmd-setconfig.go index 3fcd1f94b2..9a01168edf 100644 --- a/cmd/wsh/cmd/wshcmd-setconfig.go +++ b/cmd/wsh/cmd/wshcmd-setconfig.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var setConfigCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-setmeta.go b/cmd/wsh/cmd/wshcmd-setmeta.go index 79faa7e78c..61677f1388 100644 --- a/cmd/wsh/cmd/wshcmd-setmeta.go +++ b/cmd/wsh/cmd/wshcmd-setmeta.go @@ -12,8 +12,8 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var setMetaCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-setvar.go b/cmd/wsh/cmd/wshcmd-setvar.go index bbfb3e15a1..9efb9f9410 100644 --- a/cmd/wsh/cmd/wshcmd-setvar.go +++ b/cmd/wsh/cmd/wshcmd-setvar.go @@ -8,8 +8,8 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) const DefaultVarFileName = "var" diff --git a/cmd/wsh/cmd/wshcmd-shell-unix.go b/cmd/wsh/cmd/wshcmd-shell-unix.go index f6dedb7bcc..51a525ff37 100644 --- a/cmd/wsh/cmd/wshcmd-shell-unix.go +++ b/cmd/wsh/cmd/wshcmd-shell-unix.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/shellutil" + "github.com/woveterm/wove/pkg/util/shellutil" ) func init() { diff --git a/cmd/wsh/cmd/wshcmd-ssh.go b/cmd/wsh/cmd/wshcmd-ssh.go index 4eb1d42a4e..d4cd998672 100644 --- a/cmd/wsh/cmd/wshcmd-ssh.go +++ b/cmd/wsh/cmd/wshcmd-ssh.go @@ -7,11 +7,11 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/remote" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/remote" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wconfig" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var ( diff --git a/cmd/wsh/cmd/wshcmd-tabindicator.go b/cmd/wsh/cmd/wshcmd-tabindicator.go index c3fa499cf9..31709401fa 100644 --- a/cmd/wsh/cmd/wshcmd-tabindicator.go +++ b/cmd/wsh/cmd/wshcmd-tabindicator.go @@ -9,11 +9,11 @@ import ( "github.com/google/uuid" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/baseds" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wps" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/baseds" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wps" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var tabIndicatorCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-term.go b/cmd/wsh/cmd/wshcmd-term.go index f2119ad5b7..1871f4f453 100644 --- a/cmd/wsh/cmd/wshcmd-term.go +++ b/cmd/wsh/cmd/wshcmd-term.go @@ -9,10 +9,10 @@ import ( "path/filepath" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var termMagnified bool diff --git a/cmd/wsh/cmd/wshcmd-termscrollback.go b/cmd/wsh/cmd/wshcmd-termscrollback.go index 6368e1559d..07fea936a0 100644 --- a/cmd/wsh/cmd/wshcmd-termscrollback.go +++ b/cmd/wsh/cmd/wshcmd-termscrollback.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var termScrollbackCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-test.go b/cmd/wsh/cmd/wshcmd-test.go index 24706a1fe2..9319934bcf 100644 --- a/cmd/wsh/cmd/wshcmd-test.go +++ b/cmd/wsh/cmd/wshcmd-test.go @@ -5,7 +5,7 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var testCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-token.go b/cmd/wsh/cmd/wshcmd-token.go index 2660c12507..0d4345dded 100644 --- a/cmd/wsh/cmd/wshcmd-token.go +++ b/cmd/wsh/cmd/wshcmd-token.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/util/shellutil" + "github.com/woveterm/wove/pkg/util/shellutil" ) var tokenCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-version.go b/cmd/wsh/cmd/wshcmd-version.go index 80caab9f69..05f17002ae 100644 --- a/cmd/wsh/cmd/wshcmd-version.go +++ b/cmd/wsh/cmd/wshcmd-version.go @@ -8,10 +8,10 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wavebase" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/wavebase" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var versionVerbose bool diff --git a/cmd/wsh/cmd/wshcmd-view.go b/cmd/wsh/cmd/wshcmd-view.go index 1ba84b516f..b8ccb433f2 100644 --- a/cmd/wsh/cmd/wshcmd-view.go +++ b/cmd/wsh/cmd/wshcmd-view.go @@ -11,9 +11,9 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var viewMagnified bool diff --git a/cmd/wsh/cmd/wshcmd-wavepath.go b/cmd/wsh/cmd/wshcmd-wavepath.go index 9a5ad6af39..01650a913a 100644 --- a/cmd/wsh/cmd/wshcmd-wavepath.go +++ b/cmd/wsh/cmd/wshcmd-wavepath.go @@ -10,8 +10,8 @@ import ( "os" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var wavepathCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-web.go b/cmd/wsh/cmd/wshcmd-web.go index bfda76b82c..a461351e21 100644 --- a/cmd/wsh/cmd/wshcmd-web.go +++ b/cmd/wsh/cmd/wshcmd-web.go @@ -8,10 +8,10 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" - "github.com/wavetermdev/waveterm/pkg/wshutil" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshutil" ) var webCmd = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-workspace.go b/cmd/wsh/cmd/wshcmd-workspace.go index 6a793d68cf..41bc2d57d4 100644 --- a/cmd/wsh/cmd/wshcmd-workspace.go +++ b/cmd/wsh/cmd/wshcmd-workspace.go @@ -5,8 +5,8 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var workspaceCommand = &cobra.Command{ diff --git a/cmd/wsh/cmd/wshcmd-wsl.go b/cmd/wsh/cmd/wshcmd-wsl.go index cfe9cd47d8..b62bf51146 100644 --- a/cmd/wsh/cmd/wshcmd-wsl.go +++ b/cmd/wsh/cmd/wshcmd-wsl.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wshrpc" - "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" + "github.com/woveterm/wove/pkg/waveobj" + "github.com/woveterm/wove/pkg/wshrpc" + "github.com/woveterm/wove/pkg/wshrpc/wshclient" ) var distroName string diff --git a/cmd/wsh/main-wsh.go b/cmd/wsh/main-wsh.go index 528fd17001..5fd719f3d4 100644 --- a/cmd/wsh/main-wsh.go +++ b/cmd/wsh/main-wsh.go @@ -4,8 +4,8 @@ package main import ( - "github.com/wavetermdev/waveterm/cmd/wsh/cmd" - "github.com/wavetermdev/waveterm/pkg/wavebase" + "github.com/woveterm/wove/cmd/wsh/cmd" + "github.com/woveterm/wove/pkg/wavebase" ) // set by main-server.go diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts index 691e475443..ed464176c5 100644 --- a/emain/emain-menu.ts +++ b/emain/emain-menu.ts @@ -174,7 +174,7 @@ function makeFileMenu( function makeAppMenuItems(webContents: electron.WebContents): Electron.MenuItemConstructorOptions[] { const appMenuItems: Electron.MenuItemConstructorOptions[] = [ { - label: "About Wave Terminal", + label: "About Wove", click: (_, window) => { (getWindowWebContents(window) ?? webContents)?.send("menu-item-about"); }, diff --git a/emain/emain-web.ts b/emain/emain-web.ts index fa0f419cb1..91e69627ac 100644 --- a/emain/emain-web.ts +++ b/emain/emain-web.ts @@ -37,21 +37,115 @@ function escapeSelector(selector: string): string { export type WebGetOpts = { all?: boolean; inner?: boolean; + innertext?: boolean; + reload?: boolean; + execjs?: string; + highlight?: boolean; }; export async function webGetSelector(wc: WebContents, selector: string, opts?: WebGetOpts): Promise { if (!wc || !selector) { return null; } + + // Reload the page if requested, then wait for it to finish loading + if (opts?.reload) { + wc.reload(); + await new Promise((resolve) => { + const onFinish = () => { + wc.removeListener("did-finish-load", onFinish); + resolve(); + }; + wc.on("did-finish-load", onFinish); + // Timeout fallback in case did-finish-load doesn't fire + setTimeout(() => { + wc.removeListener("did-finish-load", onFinish); + resolve(); + }, 10000); + }); + } + + // Custom JS execution mode — run arbitrary JS and return result as string array + if (opts?.execjs) { + const customExpr = ` + (async () => { + try { + const result = await (async () => { ${opts.execjs} })(); + if (Array.isArray(result)) { + return { value: result.map(String) }; + } + return { value: [String(result)] }; + } catch (error) { + return { error: error.message }; + } + })()`; + const results = await wc.executeJavaScript(customExpr); + if (results.error) { + throw new Error(results.error); + } + return results.value; + } + const escapedSelector = escapeSelector(selector); const queryMethod = opts?.all ? "querySelectorAll" : "querySelector"; - const prop = opts?.inner ? "innerHTML" : "outerHTML"; + const prop = opts?.innertext ? "innerText" : opts?.inner ? "innerHTML" : "outerHTML"; + const doHighlight = opts?.highlight ?? false; const execExpr = ` (() => { const toArr = x => (x instanceof NodeList) ? Array.from(x) : (x ? [x] : []); try { const result = document.${queryMethod}("${escapedSelector}"); - const value = toArr(result).map(el => el.${prop}); + const els = toArr(result); + const value = els.map(el => el.${prop}); + + if (${doHighlight} && els.length > 0) { + // Inject highlight styles once + if (!document.getElementById('__wave_ai_highlight_style')) { + const style = document.createElement('style'); + style.id = '__wave_ai_highlight_style'; + style.textContent = \` + @keyframes __wave_ai_scan { + 0% { box-shadow: 0 0 0 2px rgba(99, 102, 241, 0); border-color: rgba(99, 102, 241, 0); } + 15% { box-shadow: 0 0 8px 2px rgba(99, 102, 241, 0.4); border-color: rgba(99, 102, 241, 0.8); } + 100% { box-shadow: 0 0 0 2px rgba(99, 102, 241, 0); border-color: rgba(99, 102, 241, 0); } + } + .__wave_ai_reading { + outline: 2px solid rgba(99, 102, 241, 0.7) !important; + outline-offset: 2px !important; + animation: __wave_ai_scan 2s ease-out forwards !important; + position: relative !important; + } + .__wave_ai_reading::after { + content: 'AI Reading...' !important; + position: absolute !important; + top: -22px !important; + right: 0 !important; + background: rgba(99, 102, 241, 0.9) !important; + color: white !important; + font-size: 10px !important; + padding: 2px 8px !important; + border-radius: 4px !important; + font-family: system-ui, sans-serif !important; + z-index: 999999 !important; + pointer-events: none !important; + animation: __wave_ai_scan 2s ease-out forwards !important; + } + \`; + document.head.appendChild(style); + } + + // Apply highlight to matched elements + els.forEach(el => { + el.classList.add('__wave_ai_reading'); + el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }); + + // Remove highlight after animation + setTimeout(() => { + els.forEach(el => el.classList.remove('__wave_ai_reading')); + }, 2500); + } + return { value }; } catch (error) { return { error: error.message }; diff --git a/emain/emain-wsh.ts b/emain/emain-wsh.ts index d17dc2e106..e927c3070f 100644 --- a/emain/emain-wsh.ts +++ b/emain/emain-wsh.ts @@ -27,7 +27,22 @@ export class ElectronWshClientType extends WshClient { if (wc == null) { throw new Error(`no webcontents found with blockid ${data.blockid}`); } - const rtn = await webGetSelector(wc, data.selector, data.opts); + // Sanitize opts: only allow known safe options from RPC + const safeOpts: any = {}; + if (data.opts) { + if (data.opts.all) safeOpts.all = true; + if (data.opts.inner) safeOpts.inner = true; + if (data.opts.innertext) safeOpts.innertext = true; + if (data.opts.reload) safeOpts.reload = true; + if (data.opts.highlight) safeOpts.highlight = true; + // execjs: only allow from server-side (Go backend) tool calls. + // The RPC route is trusted (server -> electron), so we allow it here + // but validate that the value is a non-empty string to prevent injection of non-string types. + if (typeof data.opts.execjs === "string" && data.opts.execjs.length > 0) { + safeOpts.execjs = data.opts.execjs; + } + } + const rtn = await webGetSelector(wc, data.selector, safeOpts); return rtn; } diff --git a/emain/emain.ts b/emain/emain.ts index 7a2b0a0710..656ac9b551 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -277,7 +277,7 @@ electronApp.on("before-quit", (e) => { type: "question", buttons: ["Cancel", "Quit"], title: "Confirm Quit", - message: "Are you sure you want to quit Wave Terminal?", + message: "Are you sure you want to quit Wove?", defaultId: 0, cancelId: 0, }); diff --git a/emain/updater.ts b/emain/updater.ts index 8f06e6bec7..3378401da5 100644 --- a/emain/updater.ts +++ b/emain/updater.ts @@ -93,8 +93,8 @@ export class Updater { // Display the update banner and create a system notification this.status = "ready"; const updateNotification = new Notification({ - title: "Wave Terminal", - body: "A new version of Wave Terminal is ready to install.", + title: "Wove", + body: "A new version of Wove is ready to install.", }); updateNotification.on("click", () => { fireAndForget(this.promptToInstallUpdate.bind(this)); diff --git a/frontend/app/aipanel/ai-utils.ts b/frontend/app/aipanel/ai-utils.ts index 8bfd67bdc0..cc13131acc 100644 --- a/frontend/app/aipanel/ai-utils.ts +++ b/frontend/app/aipanel/ai-utils.ts @@ -548,7 +548,8 @@ export const getFilteredAIModeConfigs = ( showCloudModes: boolean, inBuilder: boolean, hasPremium: boolean, - currentMode?: string + currentMode?: string, + availableSecrets?: Set ): FilteredAIModeConfigs => { const hideQuick = inBuilder && hasPremium; @@ -557,10 +558,24 @@ export const getFilteredAIModeConfigs = ( .filter((config) => !(hideQuick && config.mode === "waveai@quick")); const otherProviderConfigs = allConfigs - .filter((config) => config["ai:provider"] !== "wave") + .filter((config) => { + if (config["ai:provider"] === "wave") return false; + // Hide byok presets that need API key unless the secret exists + if (config.mode.startsWith("byok@")) { + const secretName = config["ai:apitokensecretname"]; + if (secretName) { + return config["ai:apitoken"] || (availableSecrets && availableSecrets.has(secretName)); + } + // No secret needed (local models) - show only if marker secret exists + // Set by Quick Add Model after verifying the endpoint works + return availableSecrets && availableSecrets.has("byok-local-enabled"); + } + return true; + }) .sort(sortByDisplayOrder); - const hasCustomModels = otherProviderConfigs.length > 0; + // Only count user-configured custom models, not built-in byok presets + const hasCustomModels = otherProviderConfigs.some((config) => !config.mode.startsWith("byok@")); const isCurrentModeCloud = currentMode?.startsWith("waveai@") ?? false; const shouldShowCloudModes = showCloudModes || !hasCustomModels || isCurrentModeCloud; diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index 1bfadd121d..f618042d5b 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -2,8 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { WaveStreamdown } from "@/app/element/streamdown"; -import { cn } from "@/util/util"; -import { memo, useEffect, useRef } from "react"; +import { atoms, globalStore } from "@/app/store/global"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import * as WOS from "@/app/store/wos"; +import { cn, stringToBase64 } from "@/util/util"; +import { memo, useCallback, useEffect, useRef } from "react"; import { getFileIcon } from "./ai-utils"; import { AIFeedbackButtons } from "./aifeedbackbuttons"; import { AIToolUseGroup } from "./aitooluse"; @@ -110,9 +114,33 @@ interface AIMessagePartProps { isStreaming: boolean; } +function findFirstTerminalBlockId(): string | null { + const tabId = globalStore.get(atoms.staticTabId); + const tabObj = WOS.getObjectValue(WOS.makeORef("tab", tabId)); + if (!tabObj?.blockids) return null; + for (const blockId of tabObj.blockids) { + const block = WOS.getObjectValue(WOS.makeORef("block", blockId)); + if (block?.meta?.view === "term") { + return blockId; + } + } + return null; +} + +function sendCommandToTerminal(cmd: string) { + const blockId = findFirstTerminalBlockId(); + if (!blockId) return; + const b64data = stringToBase64(cmd + "\n"); + RpcApi.ControllerInputCommand(TabRpcClient, { blockid: blockId, inputdata64: b64data }); +} + const AIMessagePart = memo(({ part, role, isStreaming }: AIMessagePartProps) => { const model = WaveAIModel.getInstance(); + const handleExecute = useCallback((cmd: string) => { + sendCommandToTerminal(cmd); + }, []); + if (part.type === "text") { const content = part.text ?? ""; @@ -125,6 +153,7 @@ const AIMessagePart = memo(({ part, role, isStreaming }: AIMessagePartProps) => parseIncompleteMarkdown={isStreaming} className="text-gray-100" codeBlockMaxWidthAtom={model.codeBlockMaxWidth} + onClickExecute={handleExecute} /> ); } diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index 3602cdd360..11008f882d 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -7,7 +7,7 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { cn, fireAndForget, makeIconClass } from "@/util/util"; import { useAtomValue } from "jotai"; -import { memo, useRef, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { getFilteredAIModeConfigs, getModeDisplayName } from "./ai-utils"; import { WaveAIModel } from "./waveai-model"; @@ -146,14 +146,23 @@ export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdow const showCloudModes = useAtomValue(getSettingsKeyAtom("waveai:showcloudmodes")); const telemetryEnabled = useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false; const [isOpen, setIsOpen] = useState(false); + const [availableSecrets, setAvailableSecrets] = useState>(new Set()); const dropdownRef = useRef(null); + // Load available secret names to filter byok presets + useEffect(() => { + RpcApi.GetSecretsNamesCommand(TabRpcClient) + .then((names) => setAvailableSecrets(new Set(names || []))) + .catch(() => {}); + }, [isOpen]); + const { waveProviderConfigs, otherProviderConfigs } = getFilteredAIModeConfigs( aiModeConfigs, showCloudModes, model.inBuilder, hasPremium, - currentMode + currentMode, + availableSecrets ); const sections: ConfigSection[] = compatibilityMode diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts index 4e78389198..cd105c8023 100644 --- a/frontend/app/aipanel/aipanel-contextmenu.ts +++ b/frontend/app/aipanel/aipanel-contextmenu.ts @@ -9,6 +9,25 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WaveAIModel } from "./waveai-model"; +async function activateByokPreset(presetKey: string, secretName: string | null, secretLabel: string | null) { + const model = WaveAIModel.getInstance(); + + if (secretName && secretLabel) { + globalStore.set(model.showApiKeyInput, { presetKey, secretName, secretLabel }); + } else { + // Local model (Ollama) - check if endpoint is reachable + try { + const resp = await fetch("http://localhost:11434/api/tags"); + if (!resp.ok) throw new Error("not reachable"); + // Mark as enabled and switch + await RpcApi.SetSecretsCommand(TabRpcClient, { "byok_local_enabled": "true" }); + model.setAIMode(presetKey); + } catch { + model.setError("Ollama is not running. Start it with: ollama serve"); + } + } +} + export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise { e.preventDefault(); e.stopPropagation(); @@ -127,6 +146,58 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo menu.push({ type: "separator" }); + const mcpEnabled = globalStore.get(model.mcpContextAtom); + if (mcpEnabled) { + menu.push({ + label: "Disconnect MCP", + click: () => { + model.setMCPContext(false); + }, + }); + } else { + menu.push({ + label: "Connect MCP...", + click: () => { + globalStore.set(model.showMCPConnectInput, true); + setTimeout(() => model.focusInput(), 0); + }, + }); + } + + menu.push({ type: "separator" }); + + const quickAddModels: ContextMenuItem[] = [ + { + label: "Claude (Anthropic)", + click: () => activateByokPreset("byok@claude-sonnet", "anthropic-api-key", "Anthropic API Key"), + }, + { + label: "GPT-5 (OpenAI)", + click: () => activateByokPreset("byok@gpt5-mini", "openai-api-key", "OpenAI API Key"), + }, + { + label: "Gemini (Google)", + click: () => activateByokPreset("byok@gemini-flash", "google-ai-key", "Google AI API Key"), + }, + { + label: "MiniMax", + click: () => activateByokPreset("byok@minimax", "minimax-api-key", "MiniMax API Key"), + }, + { + label: "Ollama (Local)", + click: () => activateByokPreset("byok@ollama", null, null), + }, + { + label: "OpenRouter", + click: () => activateByokPreset("byok@openrouter", "openrouter-api-key", "OpenRouter API Key"), + }, + ]; + + menu.push({ + label: "Quick Add Model", + submenu: quickAddModels, + }); + menu.push({ label: "Configure Modes", click: () => { diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index b903380544..e676d7f1d7 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -21,6 +21,10 @@ import { AIDroppedFiles } from "./aidroppedfiles"; import { AIModeDropdown } from "./aimode"; import { AIPanelHeader } from "./aipanelheader"; import { AIPanelInput } from "./aipanelinput"; +import { ApiKeyInput } from "./apikeyinput"; +import { MCPConnectInput, MCPDetectBanner } from "./mcpdetect"; +import { PlanProgressPanel } from "./planprogress"; +import { SessionHistoryBanner } from "./sessionhistory"; import { AIPanelMessages } from "./aipanelmessages"; import { AIRateLimitStrip } from "./airatelimitstrip"; import { WaveUIMessage } from "./aitypes"; @@ -218,6 +222,15 @@ const AIErrorMessage = memo(() => {
{errorMessage} + +
+ ) +); +ToggleSwitch.displayName = "ToggleSwitch"; + export const AIPanelHeader = memo(() => { const model = WaveAIModel.getInstance(); const widgetAccess = useAtomValue(model.widgetAccessAtom); + const mcpContext = useAtomValue(model.mcpContextAtom); const inBuilder = model.inBuilder; const handleKebabClick = (e: React.MouseEvent) => { @@ -29,42 +75,35 @@ export const AIPanelHeader = memo(() => { Wave AI -
+
{!inBuilder && ( -
- Context - Widget Context - -
+ /> + { + model.setMCPContext(!mcpContext); + setTimeout(() => model.focusInput(), 0); + }} + title={`MCP Context ${mcpContext ? "ON" : "OFF"}`} + /> + )} + {showSessionButton && onAllowSession && ( + + )}
@@ -215,6 +271,8 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { }; }, []); + const isReadTool = toolData.toolname === "read_text_file" || toolData.toolname === "read_dir"; + const handleApprove = () => { setUserApprovalOverride("user-approved"); WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-approved"); @@ -225,6 +283,14 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-denied"); }; + const handleAllowSession = () => { + const dir = extractDirFromToolDesc(toolData.tooldesc); + if (dir) { + WaveAIModel.getInstance().sessionReadApprove(dir); + } + handleApprove(); + }; + const handleMouseEnter = () => { if (!toolData.blockid) return; @@ -309,7 +375,13 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { )} {effectiveApproval === "needs-approval" && (
- +
)} {showRestoreModal && } diff --git a/frontend/app/aipanel/apikeyinput.tsx b/frontend/app/aipanel/apikeyinput.tsx new file mode 100644 index 0000000000..8b14053895 --- /dev/null +++ b/frontend/app/aipanel/apikeyinput.tsx @@ -0,0 +1,87 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { globalStore } from "@/app/store/jotaiStore"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { useAtomValue } from "jotai"; +import { memo, useCallback, useState } from "react"; +import { WaveAIModel } from "./waveai-model"; + +export const ApiKeyInput = memo(() => { + const model = WaveAIModel.getInstance(); + const inputData = useAtomValue(model.showApiKeyInput); + const [apiKey, setApiKey] = useState(""); + const [saving, setSaving] = useState(false); + + const handleSave = useCallback(async () => { + console.log("[apikeyinput] handleSave called", { apiKey: apiKey ? "***" : "empty", inputData }); + if (!apiKey.trim() || !inputData) { + console.log("[apikeyinput] early return - missing data"); + return; + } + setSaving(true); + try { + console.log("[apikeyinput] saving secret:", inputData.secretName); + await RpcApi.SetSecretsCommand(TabRpcClient, { + [inputData.secretName]: apiKey.trim(), + }); + console.log("[apikeyinput] secret saved, switching to mode:", inputData.presetKey); + model.setAIMode(inputData.presetKey); + globalStore.set(model.showApiKeyInput, null); + setApiKey(""); + } catch (e) { + console.error("[apikeyinput] Failed to save API key:", e); + } + setSaving(false); + }, [apiKey, inputData, model]); + + const handleCancel = useCallback(() => { + globalStore.set(model.showApiKeyInput, null); + setApiKey(""); + }, [model]); + + if (!inputData) return null; + + return ( +
+
+ + {inputData.secretLabel} +
+
+ setApiKey(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleSave(); + if (e.key === "Escape") handleCancel(); + }} + placeholder="sk-... or API key" + className="flex-1 bg-gray-900 text-gray-200 text-xs font-mono px-2 py-1.5 rounded border border-gray-600 focus:border-accent-500 focus:outline-none" + spellCheck={false} + autoFocus + /> + + +
+
+ Key is stored securely in Wave's secret store, not in config files. +
+
+ ); +}); + +ApiKeyInput.displayName = "ApiKeyInput"; diff --git a/frontend/app/aipanel/mcpdetect.tsx b/frontend/app/aipanel/mcpdetect.tsx new file mode 100644 index 0000000000..8cc76f07d7 --- /dev/null +++ b/frontend/app/aipanel/mcpdetect.tsx @@ -0,0 +1,199 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { globalStore } from "@/app/store/jotaiStore"; +import { WOS } from "@/store/global"; +import { getWebServerEndpoint } from "@/util/endpoints"; +import { fetch } from "@/util/fetchutil"; +import { useAtomValue } from "jotai"; +import { memo, useCallback, useEffect, useState } from "react"; +import { WaveAIModel } from "./waveai-model"; + +type MCPDetectInfo = { + found: boolean; + serverName?: string; + serverVersion?: string; + toolCount?: number; + cwd?: string; +}; + +async function detectMCPInCwd(cwd: string): Promise { + try { + const resp = await fetch(getWebServerEndpoint() + `/wave/mcp/status?cwd=${encodeURIComponent(cwd)}`); + const data = await resp.json(); + if (data.connected || data.tools) { + return { + found: true, + serverName: data.serverInfo?.name || data.serverName, + serverVersion: data.serverInfo?.version, + toolCount: data.tools?.length || 0, + cwd, + }; + } + // Server config exists but couldn't connect — still show banner + if (!data.error?.includes("no .mcp.json")) { + return { + found: true, + serverName: data.serverName, + cwd, + }; + } + return { found: false }; + } catch { + return { found: false }; + } +} + +function getTerminalCwdFromTab(tabId: string): string | null { + const tabAtom = WOS.getWaveObjectAtom(`tab:${tabId}`); + const tab = globalStore.get(tabAtom); + if (!tab?.blockids) return null; + + for (const blockId of tab.blockids) { + const blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); + const block = globalStore.get(blockAtom); + if (!block?.meta) continue; + const viewType = block.meta["view"] as string; + if (viewType !== "term") continue; + const cwd = block.meta["cmd:cwd"] as string; + if (cwd) return cwd; + } + return null; +} + +export const MCPDetectBanner = memo(({ tabId }: { tabId: string }) => { + const model = WaveAIModel.getInstance(); + const mcpEnabled = useAtomValue(model.mcpContextAtom); + const [detectInfo, setDetectInfo] = useState(null); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (mcpEnabled || dismissed) { + setDetectInfo(null); + return; + } + + const cwd = getTerminalCwdFromTab(tabId); + if (!cwd) { + setDetectInfo(null); + return; + } + + let cancelled = false; + detectMCPInCwd(cwd).then((info) => { + if (!cancelled) { + setDetectInfo(info); + } + }); + return () => { + cancelled = true; + }; + }, [tabId, mcpEnabled, dismissed]); + + const handleConnect = useCallback(() => { + model.setMCPContext(true); + setDetectInfo(null); + }, [model]); + + const handleDismiss = useCallback(() => { + setDismissed(true); + setDetectInfo(null); + }, []); + + if (!detectInfo?.found || mcpEnabled) { + return null; + } + + const serverLabel = detectInfo.serverName + ? `${detectInfo.serverName}${detectInfo.serverVersion ? ` v${detectInfo.serverVersion}` : ""}` + : ".mcp.json"; + + return ( +
+ +
+ + Found {serverLabel} + + {detectInfo.toolCount ? ( + ({detectInfo.toolCount} tools) + ) : null} +
+ + +
+ ); +}); + +MCPDetectBanner.displayName = "MCPDetectBanner"; + +export const MCPConnectInput = memo(() => { + const model = WaveAIModel.getInstance(); + const showInput = useAtomValue(model.showMCPConnectInput); + const [inputCwd, setInputCwd] = useState(""); + + const handleConnect = useCallback(() => { + if (!inputCwd.trim()) return; + model.setMCPCwd(inputCwd.trim()); + model.setMCPContext(true); + globalStore.set(model.showMCPConnectInput, false); + setInputCwd(""); + }, [inputCwd, model]); + + const handleCancel = useCallback(() => { + globalStore.set(model.showMCPConnectInput, false); + setInputCwd(""); + }, [model]); + + if (!showInput) return null; + + return ( +
+
+ + Connect to MCP server +
+
+ setInputCwd(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleConnect(); + if (e.key === "Escape") handleCancel(); + }} + placeholder="/path/to/project (with .mcp.json)" + className="flex-1 bg-gray-900 text-gray-200 text-xs font-mono px-2 py-1.5 rounded border border-gray-600 focus:border-accent-500 focus:outline-none" + spellCheck={false} + autoFocus + /> + + +
+
+ ); +}); + +MCPConnectInput.displayName = "MCPConnectInput"; diff --git a/frontend/app/aipanel/planprogress.tsx b/frontend/app/aipanel/planprogress.tsx new file mode 100644 index 0000000000..5feafeb500 --- /dev/null +++ b/frontend/app/aipanel/planprogress.tsx @@ -0,0 +1,165 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { getWebServerEndpoint } from "@/util/endpoints"; +import { fetch } from "@/util/fetchutil"; +import { memo, useCallback, useEffect, useState } from "react"; + +type PlanStep = { + id: number; + label: string; + status: string; + result?: string; + error?: string; + doneAt?: string; +}; + +type Plan = { + tabId: string; + name: string; + description?: string; + createdAt: string; + updatedAt: string; + steps: PlanStep[]; +}; + +async function fetchPlan(tabId: string): Promise { + try { + const resp = await fetch(getWebServerEndpoint() + `/wave/plan/status?tabid=${encodeURIComponent(tabId)}`); + const data = await resp.json(); + return data?.plan ?? null; + } catch { + return null; + } +} + +const statusIcon: Record = { + pending: { icon: "fa-circle", color: "text-gray-500" }, + running: { icon: "fa-spinner fa-spin", color: "text-accent-400" }, + done: { icon: "fa-check-circle", color: "text-green-400" }, + failed: { icon: "fa-times-circle", color: "text-red-400" }, + skipped: { icon: "fa-minus-circle", color: "text-gray-400" }, +}; + +const PlanStepItem = memo(({ step }: { step: PlanStep }) => { + const [expanded, setExpanded] = useState(false); + const hasDetails = step.result || step.error; + const { icon, color } = statusIcon[step.status] ?? statusIcon.pending; + + return ( +
+
hasDetails && setExpanded(!expanded)} + > + + + {step.label} + + {hasDetails && ( + + )} +
+ {expanded && hasDetails && ( +
+ {step.error && ( +
+ {step.error} +
+ )} + {step.result && ( +
+                            {step.result}
+                        
+ )} +
+ )} +
+ ); +}); +PlanStepItem.displayName = "PlanStepItem"; + +export const PlanProgressPanel = memo(({ tabId }: { tabId: string }) => { + const [plan, setPlan] = useState(null); + const [minimized, setMinimized] = useState(false); + + const refresh = useCallback(() => { + fetchPlan(tabId).then(setPlan); + }, [tabId]); + + const dismissPlan = useCallback(async () => { + try { + await fetch(getWebServerEndpoint() + `/wave/plan/delete?tabid=${encodeURIComponent(tabId)}`); + } catch {} + setPlan(null); + }, [tabId]); + + useEffect(() => { + refresh(); + const interval = setInterval(refresh, 3000); + return () => clearInterval(interval); + }, [refresh]); + + if (!plan || plan.steps.length === 0) return null; + + const doneCount = plan.steps.filter( + (s) => s.status === "done" || s.status === "failed" || s.status === "skipped" + ).length; + const totalCount = plan.steps.length; + const isComplete = doneCount === totalCount; + const progressPct = Math.round((doneCount / totalCount) * 100); + + return ( +
+ {/* Header */} +
setMinimized(!minimized)} + > + + {plan.name} + + {doneCount}/{totalCount} + + + +
+ + {/* Progress bar */} + {!minimized && ( + <> +
+
+
+ + {/* Steps */} +
+ {plan.steps.map((step) => ( + + ))} +
+ + {plan.description && ( +
+ {plan.description} +
+ )} + + )} +
+ ); +}); + +PlanProgressPanel.displayName = "PlanProgressPanel"; diff --git a/frontend/app/aipanel/sessionhistory.tsx b/frontend/app/aipanel/sessionhistory.tsx new file mode 100644 index 0000000000..32b89fb5ce --- /dev/null +++ b/frontend/app/aipanel/sessionhistory.tsx @@ -0,0 +1,103 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { getWebServerEndpoint } from "@/util/endpoints"; +import { fetch } from "@/util/fetchutil"; +import { memo, useCallback, useEffect, useState } from "react"; + +type SessionEntry = { + role: string; + text: string; + tool?: string; +}; + +type SessionLog = { + tabId: string; + timestamp: string; + model?: string; + entries?: SessionEntry[]; +}; + +async function fetchSessionHistory(tabId: string): Promise { + try { + const resp = await fetch(getWebServerEndpoint() + `/wave/session-history?tabid=${encodeURIComponent(tabId)}`); + const data = await resp.json(); + if (data?.entries && data.entries.length > 0) { + return data as SessionLog; + } + return null; + } catch { + return null; + } +} + +export const SessionHistoryBanner = memo(({ tabId }: { tabId: string }) => { + const [session, setSession] = useState(null); + const [expanded, setExpanded] = useState(false); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (dismissed) return; + let cancelled = false; + fetchSessionHistory(tabId).then((data) => { + if (!cancelled) setSession(data); + }); + return () => { + cancelled = true; + }; + }, [tabId, dismissed]); + + if (!session || dismissed) return null; + + const timestamp = new Date(session.timestamp).toLocaleString(); + const entryCount = session.entries?.length ?? 0; + + return ( +
+
setExpanded(!expanded)} + > + + + + Previous session ({timestamp}) + + {entryCount} messages + +
+ {expanded && session.entries && ( +
+ {session.entries.map((entry, idx) => ( +
+ + {entry.role === "user" ? "You" : "AI"} + + + {entry.tool && ( + [{entry.tool}] + )} + {entry.text} + +
+ ))} +
+ )} +
+ ); +}); + +SessionHistoryBanner.displayName = "SessionHistoryBanner"; diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx index 9af1d88508..70096c5d11 100644 --- a/frontend/app/aipanel/waveai-model.tsx +++ b/frontend/app/aipanel/waveai-model.tsx @@ -56,6 +56,10 @@ export class WaveAIModel { isAIStreaming = jotai.atom(false); widgetAccessAtom!: jotai.Atom; + mcpContextAtom!: jotai.Atom; + mcpCwdAtom!: jotai.Atom; + showMCPConnectInput: jotai.PrimitiveAtom = jotai.atom(false); + showApiKeyInput: jotai.PrimitiveAtom<{ presetKey: string; secretName: string; secretLabel: string } | null> = jotai.atom(null as any); droppedFiles: jotai.PrimitiveAtom = jotai.atom([]); chatId!: jotai.PrimitiveAtom; currentAIMode!: jotai.PrimitiveAtom; @@ -96,6 +100,21 @@ export class WaveAIModel { return value ?? true; }); + this.mcpContextAtom = jotai.atom((get) => { + if (this.inBuilder) { + return false; + } + const mcpContextMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:mcpcontext"); + const value = get(mcpContextMetaAtom); + return value ?? false; + }); + + this.mcpCwdAtom = jotai.atom((get) => { + const mcpCwdMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:mcpcwd"); + const value = get(mcpCwdMetaAtom); + return (value as string) ?? ""; + }); + this.codeBlockMaxWidth = jotai.atom((get) => { const width = get(this.containerWidth); return width > 0 ? width - 35 : 0; @@ -393,6 +412,20 @@ export class WaveAIModel { }); } + setMCPContext(enabled: boolean) { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: this.orefContext, + meta: { "waveai:mcpcontext": enabled }, + }); + } + + setMCPCwd(cwd: string) { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: this.orefContext, + meta: { "waveai:mcpcwd": cwd }, + }); + } + isValidMode(mode: string): boolean { const telemetryEnabled = globalStore.get(getSettingsKeyAtom("telemetry:enabled")) ?? false; if (mode.startsWith("waveai@") && !telemetryEnabled) { @@ -612,6 +645,12 @@ export class WaveAIModel { }); } + sessionReadApprove(path: string) { + RpcApi.WaveAISessionReadApproveCommand(TabRpcClient, { + path: path, + }); + } + async openDiff(fileName: string, toolcallid: string) { const chatId = this.getChatId(); diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 126f208813..642e34cf21 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -34,6 +34,7 @@ import clsx from "clsx"; import { atom, useAtomValue } from "jotai"; import { memo, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { QuickTipsViewModel } from "../view/quicktipsview/quicktipsview"; +import { MCPClientViewModel } from "../view/mcpclient/mcpclient"; import { WaveConfigViewModel } from "../view/waveconfig/waveconfig-model"; import "./block.scss"; import { BlockEnv } from "./blockenv"; @@ -54,6 +55,7 @@ BlockRegistry.set("launcher", LauncherViewModel); BlockRegistry.set("tsunami", TsunamiViewModel); BlockRegistry.set("aifilediff", AiFileDiffViewModel); BlockRegistry.set("waveconfig", WaveConfigViewModel); +BlockRegistry.set("mcpclient", MCPClientViewModel); function makeViewModel( blockId: string, diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 01346183a0..4cc0c95b61 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -31,6 +31,9 @@ export function blockViewToIcon(view: string): string { if (view == "tips") { return "lightbulb"; } + if (view == "mcpclient") { + return "plug"; + } return "square"; } @@ -56,6 +59,9 @@ export function blockViewToName(view: string): string { if (view == "tips") { return "Tips"; } + if (view == "mcpclient") { + return "MCP Client"; + } return view; } diff --git a/frontend/app/element/streamdown.tsx b/frontend/app/element/streamdown.tsx index 2426f385e2..22266d355a 100644 --- a/frontend/app/element/streamdown.tsx +++ b/frontend/app/element/streamdown.tsx @@ -148,6 +148,7 @@ const CodeBlock = ({ children, onClickExecute, codeBlockMaxWidthAtom }: CodeBloc }; const language = getLanguage(children); + const isShellLanguage = /^(bash|sh|shell|zsh|fish|ksh|csh|tcsh|powershell|pwsh|cmd|bat)$/i.test(language); return (
{language}
- {onClickExecute && ( + {onClickExecute && isShellLanguage && ( { + if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaisessionreadapprove", data, opts); + return client.wshRpcCall("waveaisessionreadapprove", data, opts); + } + // command "waveaitoolapprove" [call] WaveAIToolApproveCommand(client: WshClient, data: CommandWaveAIToolApproveData, opts?: RpcOpts): Promise { if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaitoolapprove", data, opts); diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx index 9d96d290c2..fc48ec22dc 100644 --- a/frontend/app/view/aifilediff/aifilediff.tsx +++ b/frontend/app/view/aifilediff/aifilediff.tsx @@ -96,8 +96,8 @@ function AiFileDiffView({ blockId, model }: ViewComponentProps = { + ts: "typescript", tsx: "typescriptreact", + js: "javascript", jsx: "javascriptreact", + php: "php", vue: "html", + py: "python", rb: "ruby", + go: "go", rs: "rust", java: "java", + css: "css", scss: "scss", less: "less", + html: "html", json: "json", + yaml: "yaml", yml: "yaml", + md: "markdown", sql: "sql", + sh: "shell", bash: "shell", zsh: "shell", + xml: "xml", svg: "xml", + c: "c", cpp: "cpp", h: "c", + cs: "csharp", swift: "swift", kt: "kotlin", + blade: "html", +}; + +function getLanguageFromFileName(fileName: string): string | undefined { + const ext = fileName.split(".").pop()?.toLowerCase(); + // Handle compound extensions like .blade.php + const parts = fileName.split("."); + if (parts.length >= 3) { + const compound = parts.slice(-2).join("."); + if (compound === "blade.php") return "html"; + } + return ext ? extToLanguage[ext] : undefined; +} + function defaultDiffEditorOptions(): MonacoTypes.editor.IDiffEditorOptions { const opts: MonacoTypes.editor.IDiffEditorOptions = { scrollBeyondLastLine: false, @@ -67,7 +95,7 @@ export function DiffViewer({ blockId, original, modified, language, fileName }: original={original} modified={modified} options={editorOpts} - language={language} + language={language ?? getLanguageFromFileName(fileName)} />
diff --git a/frontend/app/view/mcpclient/mcpclient.tsx b/frontend/app/view/mcpclient/mcpclient.tsx new file mode 100644 index 0000000000..b6165ad948 --- /dev/null +++ b/frontend/app/view/mcpclient/mcpclient.tsx @@ -0,0 +1,424 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { BlockNodeModel } from "@/app/block/blocktypes"; +import { RpcApi } from "@/app/store/wshclientapi"; +import type { TabModel } from "@/app/store/tab-model"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { WOS } from "@/store/global"; +import { getWebServerEndpoint } from "@/util/endpoints"; +import { fetch } from "@/util/fetchutil"; +import { Atom, atom, useAtomValue } from "jotai"; +import React, { memo, useCallback, useEffect, useState } from "react"; + +type MCPTool = { + name: string; + title?: string; + description: string; + inputSchema: Record; +}; + +type MCPServerInfo = { + name: string; + version: string; +}; + +type MCPStatusResponse = { + connected: boolean; + serverName?: string; + serverInfo?: MCPServerInfo; + tools?: MCPTool[]; + error?: string; +}; + +type MCPCallLogEntry = { + timestamp: string; + toolName: string; + duration: number; + error?: string; + resultLen: number; + arguments?: Record; + result?: string; +}; + +type MCPCallResponse = { + result?: string; + error?: string; + duration?: number; +}; + +function mcpUrl(path: string): string { + return getWebServerEndpoint() + path; +} + +async function fetchMCPStatus(cwd: string): Promise { + const resp = await fetch(mcpUrl(`/wave/mcp/status?cwd=${encodeURIComponent(cwd)}`)); + return resp.json(); +} + +async function callMCPTool(cwd: string, toolName: string, args?: Record): Promise { + const resp = await fetch(mcpUrl("/wave/mcp/call"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ cwd, toolName, arguments: args || {} }), + }); + return resp.json(); +} + +async function fetchCallLog(): Promise { + const resp = await fetch(mcpUrl("/wave/mcp/calllog")); + return resp.json(); +} + +// ── Tool Run Dialog ────────────────────────────────────────────── + +const ToolRunDialog = memo( + ({ + tool, + cwd, + onClose, + }: { + tool: MCPTool; + cwd: string; + onClose: () => void; + }) => { + const [argsText, setArgsText] = useState("{}"); + const [result, setResult] = useState(null); + const [running, setRunning] = useState(false); + + const handleRun = useCallback(async () => { + setRunning(true); + setResult(null); + try { + const args = JSON.parse(argsText); + const resp = await callMCPTool(cwd, tool.name, args); + setResult(resp); + } catch (e: any) { + setResult({ error: e.message }); + } + setRunning(false); + }, [cwd, tool.name, argsText]); + + return ( +
+
+

{tool.name}

+ +
+

{tool.description}

+ +
+ +