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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ 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.4] - 2026-06-18

### Added

- 📱 **Full PWA support.** cptr can now be installed as a standalone app on phones, tablets, and desktops. Includes offline caching, an offline fallback page, home screen shortcuts (New Chat, Open Workspace, New Note, New Terminal, Search), and a service worker that keeps static assets available when the server is unreachable.
- 📤 **Share target.** Share files, text, or links from other apps directly into cptr. On mobile, use the system share sheet to send content straight to a chat.
- 📂 **File handling.** Opening supported file types (code, documents, images) with cptr now imports them into your workspace with a folder picker.
- 📊 **Context usage indicator.** The chat panel now shows how full the context window is, so you can see at a glance how much room is left before compaction kicks in.
- 🗂️ **Workspace picker for imports.** When importing shared files or opening files from outside the app, a workspace picker lets you choose where to save them.
- 🔧 **PWA settings tab.** A new tab in Settings shows your install status, lets you check for service worker updates, and clear the offline cache.
- 🖥️ **Status modal.** A new status indicator in the chat panel shows server connection state and context usage at a glance.

### Changed

- 🌐 **Perplexity base URL is now configurable.** You can point the Perplexity search provider at a custom endpoint (like a LiteLLM proxy) from Settings or via the `PERPLEXITY_BASE_URL` environment variable.
- 🤖 **Simplified default system prompt.** Removed the instruction that told the AI to always create a plan before acting. The AI now helps directly unless you ask it to plan.
- 📂 **File uploads no longer overwrite existing files.** Uploading a file with the same name as an existing one now automatically adds a number suffix instead of replacing it.
- 🌍 **New translation keys.** Added PWA, share, file handling, and status labels across supported languages.

## [0.5.3] - 2026-06-17

### Added
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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.
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, your computer, any device.

## Install

Expand Down Expand Up @@ -136,6 +136,9 @@ Close the tab. Come back tomorrow on any device. Everything is where you left it

Life is short. Touch grass. Read our [Manifesto](MANIFESTO.md).

> Help us build open, empowering tools that put AI in people's hands.
> [We're hiring](http://careers.openwebui.com/).

## Docker

Run cptr with Docker:
Expand Down
109 changes: 109 additions & 0 deletions cptr/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,14 @@ async def pwa_manifest():
name = f"cptr @ {hostname}" if hostname else "cptr"

return {
"id": "/",
"name": name,
"short_name": "cptr",
"description": f"Your computer, from anywhere. v{version}",
"start_url": "/",
"scope": "/",
"display": "standalone",
"display_override": ["window-controls-overlay", "standalone"],
"orientation": "any",
"background_color": "#000000",
"theme_color": "#000000",
Expand All @@ -348,6 +351,112 @@ async def pwa_manifest():
},
],
"categories": ["developer", "productivity", "utilities"],
"launch_handler": {"client_mode": ["navigate-existing", "auto"]},
"handle_links": "preferred",
"note_taking": {"new_note_url": "/?intent=newNote"},
"shortcuts": [
{
"name": "New Chat",
"short_name": "Chat",
"url": "/?intent=newChat",
"icons": [{"src": "/icon-192.png", "sizes": "192x192"}],
},
{
"name": "Open Workspace",
"short_name": "Workspace",
"url": "/?intent=openWorkspace",
"icons": [{"src": "/icon-192.png", "sizes": "192x192"}],
},
{
"name": "New Note",
"short_name": "Note",
"url": "/?intent=newNote",
"icons": [{"src": "/icon-192.png", "sizes": "192x192"}],
},
{
"name": "New Terminal",
"short_name": "Terminal",
"url": "/?intent=newTerminal",
"icons": [{"src": "/icon-192.png", "sizes": "192x192"}],
},
{
"name": "Search",
"short_name": "Search",
"url": "/?intent=search",
"icons": [{"src": "/icon-192.png", "sizes": "192x192"}],
},
],
"share_target": {
"action": "/?intent=share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{
"name": "files",
"accept": [
"text/*",
"application/pdf",
"image/*",
".md",
".py",
".js",
".ts",
".tsx",
".jsx",
".svelte",
".json",
".yaml",
".yml",
".toml",
".rs",
".go",
],
}
],
},
},
"file_handlers": [
{
"action": "/?intent=importFiles",
"accept": {
"text/*": [
".txt",
".md",
".py",
".js",
".ts",
".tsx",
".jsx",
".svelte",
".css",
".html",
".json",
".yaml",
".yml",
".toml",
".rs",
".go",
".java",
".c",
".cpp",
".h",
".hpp",
".sh",
],
"application/pdf": [".pdf"],
"image/*": [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"],
},
}
],
"protocol_handlers": [
{"protocol": "web+cptr", "url": "/?intent=%s"},
],
"prefer_related_applications": False,
"related_applications": [],
}


Expand Down
2 changes: 2 additions & 0 deletions cptr/frontend/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="cptr" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="apple-touch-startup-image" href="/icon-512.png" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
Expand Down
27 changes: 26 additions & 1 deletion cptr/frontend/src/lib/apis/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@ export interface ChatInfo {
is_active?: boolean;
}

export interface ContextUsage {
tokens: number;
estimated_tokens: number;
threshold: number;
percent: number;
source: 'estimated';
}

export interface ChatDetail {
chat: ChatInfo;
messages: ChatMessageRow[];
context_usage?: ContextUsage | null;
}

export interface SendMessageResult {
Expand All @@ -48,6 +57,16 @@ export interface ChatSendParams {
voice_mode?: boolean;
}

export interface CompactChatResult {
ok: boolean;
compacted: boolean;
reason?: string;
dropped_messages?: number;
kept_messages?: number;
summary_chars?: number;
context_usage?: ContextUsage | null;
}

// ── Queries ─────────────────────────────────────────────────

export const getChats = (
Expand All @@ -61,7 +80,10 @@ export const getChats = (
`/api/chats?workspace=${encodeURIComponent(workspace)}&limit=${limit}&offset=${offset}&sort_by=${sortBy}&sort_dir=${sortDir}`
);

export const getChat = (chatId: string) => fetchJSON<ChatDetail>(`/api/chats/${chatId}`);
export const getChat = (chatId: string, modelId?: string) => {
const suffix = modelId ? `?model_id=${encodeURIComponent(modelId)}` : '';
return fetchJSON<ChatDetail>(`/api/chats/${chatId}${suffix}`);
};

export const deleteChat = (chatId: string) =>
fetchJSON<{ ok: boolean }>(`/api/chats/${chatId}`, { method: 'DELETE' });
Expand Down Expand Up @@ -106,6 +128,9 @@ export const approveToolCall = (
export const cancelTask = (chatId: string, messageId: string) =>
fetchJSON(`/api/chats/${chatId}/messages/${messageId}/cancel`, { method: 'POST' });

export const compactChat = (chatId: string, modelId: string) =>
fetchJSON<CompactChatResult>(`/api/chats/${chatId}/compact`, jsonBody({ model_id: modelId }));

export const updateCurrentMessage = (chatId: string, messageId: string) =>
fetchJSON<{ ok: boolean }>(`/api/chats/${chatId}/current`, jsonBody({ message_id: messageId }));

Expand Down
Loading
Loading