diff --git a/README.md b/README.md index 15ad8f9..af2660f 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,17 @@ One origin means no CORS, no double auth, and SSE streaming included. **It works | --- | --- | | 🗂️ **Project picker** | Start page with known projects and recents. Open any folder by path or browse the server filesystem. Everything is scoped per project via opencode's `?directory=` API. | | 💬 **Live chat** | SSE streaming with markdown, collapsible thinking, tool calls with input/output, per-step tokens & cost, timestamps, abort, a prompt queue, and permission and question prompts that survive reloads. Message actions: copy, read aloud, edit & resend, fork. | -| ⌨️ **Autocomplete like opencode** | `@` mentions files, folders and subagents (attached the way opencode does), `/` runs commands, `!` switches to shell mode. On phones the options row hides while the keyboard is open. | -| 🧠 **Model + think level** | Model picker across all providers with pricing & context details, think-level selector for reasoning models, agent picker. Persisted per project. | +| ⌨️ **Input like opencode's TUI** | `@` mentions files, folders and subagents (attached the way opencode does), `/` runs commands, `!` switches to shell mode, **Tab** cycles agents, **↑/↓** recall earlier prompts, **Esc Esc** stops a reply. **Paste screenshots or drop files** anywhere on the chat. Every option has a **?** that explains it and links to its settings. On phones the options row hides while the keyboard is open. | +| 🧠 **Model + think level** | Model picker across all providers with pricing & context details (long lists open a roomy searchable grid), think-level selector for reasoning models, agent picker. Persisted per project. Chat width: comfortable, wide or full. | +| 🧑‍🔧 **Agents** | View, create and edit agents: description, mode, model, prompt (drafted by AI if you like), built-in tools and **which MCP servers each agent may use**. [More →](docs/mcp.md#agents) | +| 🎓 **Skills** | Every skill opencode can load, with what it says, where it comes from, **quality checks**, and auto / ask / off per skill. Write new skills or **improve** any of them with AI; a **skill library** served by the web app overrides a skill of the same name. Also from the API and agent control. [More →](docs/mcp.md#skills) | +| 📊 **Usage & cost** | Group usage by session, day, hour, weekday, model, provider or agent; **real cost** next to the **list-price estimate**, so promotions and free tiers show what they save. | | 🔌 **MCP manager** | Per-project and global MCP status, off / ask / auto modes per server and per tool, presets, and add remote or local servers from a catalog, a URL, a command line, pasted JSON or a form. **Per-conversation MCP selection** in the prompt box. Tool lists are discovered from the servers themselves over **Streamable HTTP, legacy SSE or stdio**. Every server has a **details view**: why it fails and how to fix it, its tools with full descriptions and parameters, an **editor** that never exposes stored secrets, and a step-by-step **connection test**. [More →](docs/mcp.md) | -| 🤖 **Agent control** | One click registers this app's MCP server with opencode, so **the chat can manage opencode itself**: test, add and reconnect MCP servers, reload the config (after running replies finish), read and prompt other chats, see task lists, and manage schedules. State-changing tools ask first. [More →](docs/mcp.md#agent-control) | +| 🤖 **Agent control** | On by default: this app's MCP server is registered with opencode, so **the chat can manage opencode itself**: test, add, switch and reconnect MCP servers (applied at once), reload the config and **continue the chat afterwards**, run any agent in a **sub-chat**, read and prompt other chats, see task lists, and manage schedules. State-changing tools ask first. [More →](docs/mcp.md#agent-control) | | ⏰ **Scheduled prompts** | Cron-timed prompts per project (every weekday at 08:00, every 30 minutes…), in a new session each run or always the same one, with run history and links to the chats. [More →](docs/mcp.md#scheduled-prompts) | | 🧩 **MCP UI apps** | Tool results render live in sandboxed iframes: **MCP Apps** (SEP-1865 JSON-RPC host, theme & display-mode aware), mcp-ui **raw HTML**, **external URLs** and **remote-dom** components. Apps can move to a side panel or fullscreen, with shareable `?app=` links. Tools aren't re-run silently: only read-only ones are, and the rest get a **Render UI** button. A built-in demo server (`/mcp-demo`) showcases every flavour. [More →](docs/mcp.md#mcp-ui-apps) | | 🔑 **Provider config from the UI** | "Configure providers…" right inside the model dropdown adds or updates API keys without touching the server. Keys are never sent back to the browser. | -| 📱 **Mobile first** | The desktop sidebar (resizable, collapsible to an icon rail, optional projects panel) becomes a slideover on phones. There are touch-sized controls, safe-area aware layouts and a prompt box that stays above the keyboard, so you can continue any session from your phone. Installable as a PWA. | +| 📱 **Mobile first** | The desktop sidebar (resizable, collapsible to an icon rail, optional projects panel that lists each project's starred, recent, unread and running chats, all configurable) becomes a slideover on phones. There are touch-sized controls, safe-area aware layouts and a prompt box that stays above the keyboard, so you can continue any session from your phone. Installable as a PWA. | | 🔄 **Any opencode version** | Works with every opencode 1.x server (1.0 → 1.19) and with **opencode 2.x** (`@opencode/cli`), which only speaks the new **v2 API** (`/api/*`): the web app detects which one it talks to and translates, so the UI behaves the same, MCP, config and subagents included. Features a server lacks are hidden instead of failing. [More →](docs/compatibility.md) | | 🕸️ **Subagent per MCP** | One switch creates a specialist subagent per MCP server (only that server's tools) and an `@orchestrator` that knows what to send each one, keeping its own context free of MCP tools. Summaries are editable and stay in sync as servers change. [More →](docs/mcp.md#subagent-per-mcp) | | 🤖 **Subagents** | When the agent hands work to a subagent, the chat shows a live card (agent, steps, result) with a link into the subagent's own conversation and back; the sidebar nests subagent sessions under their parent. | @@ -196,8 +199,9 @@ Tools: | area | tools | | --- | --- | | overview | `server_info` (version, protocol, features, busy sessions, schedules, last reload) | -| chats | `list_projects`, `create_project`, `list_sessions`, `session_status`, `read_session`, `get_messages`, `create_session`, `send_prompt` (waits for the reply, or `wait: false`), `abort_session`, `fork_session`, `list_tasks` | -| opencode | `list_models`, `list_agents`, `list_commands`, `list_skills`, `get_config` (redacted), `reload_opencode` | +| chats | `list_projects`, `create_project`, `list_sessions`, `session_status`, `current_session`, `read_session`, `get_messages`, `create_session`, `send_prompt` (waits for the reply, or `wait: false`), `run_subagent` (any agent in a sub-chat), `abort_session`, `fork_session`, `list_tasks` | +| opencode | `list_models`, `list_agents`, `list_commands`, `get_config` (redacted), `reload_opencode` | +| skills | `list_skills`, `get_skill` (content + checks), `save_skill` (library), `delete_skill`, `set_skill_permission`, `improve_skill` (AI) | | MCP | `list_mcp` (status, errors with a fix, tools), `mcp_status`, `test_mcp`, `add_mcp`, `connect_mcp`, `disconnect_mcp`, `set_mcp_mode`, `set_tool_mode`, `mcp_subagents`, `configure_mcp_subagents` | | schedules | `list_schedules`, `create_schedule`, `update_schedule`, `delete_schedule`, `run_schedule` | @@ -220,6 +224,7 @@ Read-only tools carry `readOnlyHint`. Register the same server **with opencode i | `NUXT_PUBLIC_DEMO_MCP_URL` | *(browser origin)* | URL at which opencode reaches the built-in `/mcp-demo` server (`http://web:3000/mcp-demo` in compose) | | `NUXT_MCP_LOCAL_DISCOVERY` | `always` | `always` / `same-host` / `never`: whether the web app spawns local MCP servers to list their tools | | `NUXT_SELF_URL` | *(origin of the demo URL, else the request)* | URL at which opencode reaches this app; agent control registers `/mcp` (`http://web:3000` in compose) | +| `NUXT_AGENT_CONTROL` | `auto` | `auto` registers agent control in opencode's global config once (switching it off is respected); `off` never does | | `NUXT_SCHEDULER` | `on` | `off` stops firing scheduled prompts (e.g. a second replica on the same data volume) | | `TZ` | *(container default, UTC)* | time zone of scheduled prompts, e.g. `Europe/Madrid` | diff --git a/app/assets/css/main.css b/app/assets/css/main.css index d2357ea..c87d793 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -106,18 +106,29 @@ body { /* ---- motion system ---- */ -/* page transitions */ -.oc-page-enter-active, +/* page transitions: a short fade only. The project shell (sidebar, header) + stays mounted while its content changes, so nothing around the content + moves; old content leaves fast so the new one isn't kept waiting */ +.oc-page-enter-active { + transition: opacity 0.14s ease-out; +} .oc-page-leave-active { - transition: opacity 0.15s ease, transform 0.15s ease; + transition: opacity 0.08s ease-in; } -.oc-page-enter-from { +.oc-page-enter-from, +.oc-page-leave-to { opacity: 0; - transform: translateY(6px); } -.oc-page-leave-to { + +/* cross-fade of two contents stacked in the same box (sidebar <-> rail): + both are absolutely positioned by their parent, so no layout shift */ +.oc-xfade-enter-active, +.oc-xfade-leave-active { + transition: opacity 0.16s ease; +} +.oc-xfade-enter-from, +.oc-xfade-leave-to { opacity: 0; - transform: translateY(-4px); } /* vertical collapse (options panels, tool details, reasoning) */ @@ -222,9 +233,6 @@ body { .oc-tile { transition: background-color 0.15s ease, color 0.15s ease, transform 0.12s ease; } -.oc-tile:hover { - transform: translateY(-1px); -} .oc-tile:active { transform: scale(0.96); } @@ -262,17 +270,17 @@ body { animation: oc-send-up 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; } -/* interactive rows/buttons: smooth hover feedback */ +/* interactive rows/buttons: smooth hover feedback (colour only: rows that + shift sideways under the pointer read as jitter in long lists) */ .oc-row { - transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease; -} -.oc-row:hover { - transform: translateX(2px); + transition: background-color 0.15s ease, color 0.15s ease; } @media (prefers-reduced-motion: reduce) { .oc-page-enter-active, .oc-page-leave-active, + .oc-xfade-enter-active, + .oc-xfade-leave-active, .oc-collapse-enter-active, .oc-collapse-leave-active, .oc-slide-enter-active, @@ -406,3 +414,51 @@ body { color: var(--ui-text-highlighted); border-color: var(--ui-border-inverted); } + +/* filled variant of an outline (lucide) icon, e.g. a favourite star */ +.oc-filled path, +.oc-filled polygon, +.oc-filled circle { + fill: currentColor; +} + +/* long-press label on touch screens (plugins/longpress-tip.client.ts) */ +.oc-touch-tip { + position: fixed; + z-index: 100; + max-width: min(18rem, calc(100vw - 1rem)); + padding: 0.3rem 0.55rem; + border-radius: 0.25rem; + background: var(--ui-bg-inverted); + color: var(--ui-bg); + font-size: 0.75rem; + line-height: 1.3; + pointer-events: none; + animation: oc-fade-up 0.14s ease both; +} +@media (pointer: coarse) { + /* a long press shows the label above instead of iOS's link preview */ + button, + a[aria-label] { + -webkit-touch-callout: none; + } +} + +/* brief highlight when a value changes from the keyboard (Tab → agent) */ +@keyframes oc-flash { + 0% { background-color: var(--ui-bg-inverted); color: var(--ui-bg); } + 100% { background-color: var(--ui-bg-elevated); } +} +.oc-flash { + animation: oc-flash 0.6s ease-out; +} +@media (prefers-reduced-motion: reduce) { + .oc-flash { animation: none; } +} + +/* MCP apps and data tables in the chat: when the column is narrower than the + scroller (@container), they grow into the free space on both sides, 1rem + from its edges; never narrower than the column */ +.oc-wide-apps .oc-breakout { + margin-inline: min(0px, calc(50% - 50cqw + 1rem)); +} diff --git a/app/components/AgentControlCard.vue b/app/components/AgentControlCard.vue index f171bc8..47132ad 100644 --- a/app/components/AgentControlCard.vue +++ b/app/components/AgentControlCard.vue @@ -53,9 +53,10 @@ async function install(enabled = true) { } const TOOLS = [ - ['MCP', 'list, test, add, reconnect, set modes'], - ['Reload', 'one project or all of opencode, after replies finish'], + ['MCP', 'list, test, add, switch on/off at once, set modes'], + ['Reload', 'one project or all of opencode; the chat continues by itself afterwards'], ['Chats', 'list, read and prompt other sessions, abort'], + ['Sub-chats', 'run any agent on a task in a sub-chat (run_subagent)'], ['Tasks', 'todo lists of busy and recent sessions'], ['Schedules', 'create, run and manage scheduled prompts'], ['Info', 'version, models, agents, commands, skills, config'] @@ -81,7 +82,7 @@ const TOOLS = [
-
+
{{ title }}
{{ text }}
@@ -123,7 +124,7 @@ const TOOLS = [ label="Tools" @click="emit('details', state.name)" /> - Saved in the global config; new chats get the tools. + On by default, saved in the global config: every chat lists it among its MCP servers. Switching it off here is remembered.
diff --git a/app/components/HScroll.vue b/app/components/HScroll.vue index cdb8ef9..65797f3 100644 --- a/app/components/HScroll.vue +++ b/app/components/HScroll.vue @@ -11,6 +11,13 @@ function update() { canRight.value = node.scrollLeft + node.clientWidth < node.scrollWidth - 4 } +const mask = computed(() => { + if (!canLeft.value && !canRight.value) return undefined + const left = canLeft.value ? 'transparent 0, #000 2.5rem' : '#000 0' + const right = canRight.value ? '#000 calc(100% - 2.5rem), transparent 100%' : '#000 100%' + return `linear-gradient(to right, ${left}, ${right})` +}) + function scrollBy(direction: 1 | -1) { el.value?.scrollBy({ left: direction * el.value.clientWidth * 0.8, behavior: 'smooth' }) } @@ -56,9 +63,11 @@ onBeforeUnmount(() => observer?.disconnect())