diff --git a/CHANGELOG.md b/CHANGELOG.md index e9b7008..ffa59fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.3] - 2026-06-17 + +### Added + +- 🔊 **Auto-stream TTS.** New toggle in Audio settings that automatically reads AI responses aloud as they stream in, without needing to manually enable playback each time. +- 🔀 **Branch management.** You can now rename and delete branches directly from the Git panel. Right-click (or tap the action button) on any branch to see your options. +- 🔍 **Branch search.** The branch picker now has a search bar so you can quickly find branches in large repos. +- 💾 **Stash awareness when switching branches.** If you have uncommitted changes and try to switch or create a branch, you'll be asked whether to bring the changes along or leave them behind. + +### Fixed + +- 🧩 **MCP tool server errors are now helpful.** If the MCP package isn't installed, you'll get a clear message telling you how to install it instead of a confusing traceback. +- 🔧 **AI tool calls no longer break on empty arrays.** Fixed a subtle issue where providers returning empty tool call lists could cause errors during streaming. +- 🗂️ **Git file list no longer shows duplicates.** Files that were both staged and modified used to appear twice in the changed files list. They now show as a single entry with the correct status. + +### Changed + +- 📋 **Dropdown menus support action buttons.** Menu items can now have a secondary action icon on the right side, used for things like branch context menus. +- 📂 **Home page lists are trimmed.** The recent files and folder suggestions on the welcome screen now show at most 5 items each to keep things tidy. +- 📦 **Added an "all" install extra.** You can now `pip install cptr[all]` to get every optional dependency (MCP, document support, PAM) in one go. +- 🔊 **Sticky save button in Audio settings.** The save button in Audio settings now stays visible at the bottom of the panel as you scroll. +- 💾 **Stash includes untracked files.** When stashing changes, new files that haven't been committed yet are now included automatically. + ## [0.5.2] - 2026-06-16 ### Fixed diff --git a/Dockerfile b/Dockerfile index eed06bd..25762cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,8 @@ WORKDIR /home/cptr # Install the wheel into an isolated venv COPY --chown=cptr:cptr --from=backend-builder /dist/*.whl /tmp/ RUN uv venv /home/cptr/.venv && \ - uv pip install --python /home/cptr/.venv/bin/python /tmp/*.whl && \ + set -- /tmp/*.whl && \ + uv pip install --python /home/cptr/.venv/bin/python "$1[all]" && \ rm /tmp/*.whl ENV PATH="/home/cptr/.venv/bin:$PATH" diff --git a/README.md b/README.md index b308131..dc50b34 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ `cptr` (short for "computer") runs on your machine and serves your whole computer (files, terminal, editor, git) to any browser. It literally is your computer. -Phone, tablet, laptop, another computer, even the one it's running on. Designed to feel native on every screen. Plug in an AI that can actually read, write, and run things on your machine, or bring your favourite terminal agent. Terminal multiplexer, parallel AI agents, full workstation, one tool, any device. +Use it from your phone, tablet, laptop, another computer, or the machine it's running on. Designed to feel native on every screen. Plug in an AI that can actually read, write, and run things on your machine, or bring your favourite terminal agent. Terminal multiplexer, parallel AI agents, full workstation, one tool, any device. ## Install @@ -25,6 +25,10 @@ pip install cptr cptr run ``` +MCP tool servers require the optional MCP dependencies: `pip install 'cptr[mcp]'`. +To install every optional feature group, use `pip install 'cptr[all]'`. +The Docker image includes all optional feature groups. + Or with [uv](https://docs.astral.sh/uv/): `uvx cptr@latest run` Opens in your browser at `http://localhost:8000`. diff --git a/cptr/frontend/src/lib/apis/git.ts b/cptr/frontend/src/lib/apis/git.ts index 17e90c7..6bf0082 100644 --- a/cptr/frontend/src/lib/apis/git.ts +++ b/cptr/frontend/src/lib/apis/git.ts @@ -45,6 +45,9 @@ export const getGitShow = (root: string, ref: string) => export const getGitBranches = (root: string) => fetchJSON(`/api/git/branches?root=${encodeURIComponent(root)}`); +export const getGitStashes = (root: string) => + fetchJSON(`/api/git/stashes?root=${encodeURIComponent(root)}`); + export const stageFiles = (root: string, files: string[]) => fetchJSON('/api/git/stage', jsonBody({ root, files })); @@ -72,9 +75,29 @@ export const gitPush = ( export const gitUncommit = (root: string) => fetchJSON('/api/git/uncommit', jsonBody({ root })); +export const gitStash = (root: string, message?: string) => + fetchJSON('/api/git/stash', jsonBody({ root, message })); + +export const gitUnstash = (root: string, index = 0) => + fetchJSON('/api/git/unstash', jsonBody({ root, index })); + export const createGitBranch = (root: string, name: string) => fetchJSON('/api/git/branch', jsonBody({ root, name })); +export const renameGitBranch = (root: string, old_name: string, new_name: string) => + fetchJSON('/api/git/branch', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ root, old_name, new_name }) + }); + +export const deleteGitBranch = (root: string, name: string) => + fetchJSON('/api/git/branch', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ root, name }) + }); + export const checkoutBranch = (root: string, branch: string) => fetchJSON('/api/git/checkout', jsonBody({ root, branch })); diff --git a/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte b/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte index e25a847..7c05c4d 100644 --- a/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte +++ b/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte @@ -25,6 +25,7 @@ let ttsVoice = $state('alloy'); let ttsFormat = $state('mp3'); let ttsPlaybackSpeed = $state(1); + let ttsAutoStreamEnabled = $state(false); let hasExistingTtsKey = $state(false); let voiceModeSystemPrompt = $state(''); let voiceModeSttMode = $state<'browser' | 'provider'>('browser'); @@ -49,6 +50,7 @@ ttsFormat = (config['audio.tts_format'] as string) || 'mp3'; const speed = Number(config['audio.tts_playback_speed']); ttsPlaybackSpeed = Number.isFinite(speed) ? Math.min(Math.max(speed, 0.5), 2) : 1; + ttsAutoStreamEnabled = config['audio.tts_auto_stream_enabled'] === true; hasExistingTtsKey = !!config['audio.tts_api_key']; voiceModeSystemPrompt = (config['audio.voice_mode_system_prompt'] as string) || ''; voiceModeSttMode = @@ -72,6 +74,7 @@ 'audio.tts_voice': ttsVoice, 'audio.tts_format': ttsFormat, 'audio.tts_playback_speed': ttsPlaybackSpeed, + 'audio.tts_auto_stream_enabled': ttsAutoStreamEnabled, 'audio.voice_mode_system_prompt': voiceModeSystemPrompt, 'audio.voice_mode_stt_mode': voiceModeSttMode }; @@ -105,23 +108,41 @@
{$t('admin.audio.voiceMemosHint')}
- {transcribeEnabled ? $t('admin.audio.transcribeOnHint') : $t('admin.audio.transcribeOffHint')} + {transcribeEnabled + ? $t('admin.audio.transcribeOnHint') + : $t('admin.audio.transcribeOffHint')}
- {quality === 'high' ? $t('admin.audio.qualityHintHigh') : quality === 'medium' ? $t('admin.audio.qualityHintMedium') : $t('admin.audio.qualityHintLow')} + {quality === 'high' + ? $t('admin.audio.qualityHintHigh') + : quality === 'medium' + ? $t('admin.audio.qualityHintMedium') + : $t('admin.audio.qualityHintLow')}
{$t('admin.audio.ttsEnabledHint')}
+ ++ {$t('admin.audio.ttsAutoStreamHint')} +
@@ -263,11 +323,15 @@
- {voiceModeSttMode === 'browser' ? $t('admin.audio.voiceModeBrowserSttHint') : $t('admin.audio.voiceModeProviderSttHint')} + {voiceModeSttMode === 'browser' + ? $t('admin.audio.voiceModeBrowserSttHint') + : $t('admin.audio.voiceModeProviderSttHint')}