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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Most AI tools require accounts, API keys, or subscriptions that bill you per tok
- **Isolated sandbox:** optionally run models in a hardened Docker container with capability dropping, read-only volumes, and localhost-only networking
- **Image input:** paste or drag images and screenshots directly into the chat
- **Screen capture:** type `/screen` to instantly capture your entire screen and attach it to your question as context
- **Slash commands:** built-in prompt shortcuts for common tasks: `/translate`, `/rewrite`, `/tldr`, `/refine`, `/bullets`, `/todos`. Highlight text anywhere, summon Thuki, type a command, and hit Enter
- **Slash commands:** built-in commands for live search and prompt shortcuts: `/search`, `/translate`, `/rewrite`, `/tldr`, `/refine`, `/bullets`, `/todos`. Highlight text anywhere, summon Thuki, type a command, and hit Enter
- **Extended reasoning:** type `/think` to have the model reason through a problem step by step before answering
- **Privacy-first:** zero-trust architecture, all data stays on your device

Expand Down
90 changes: 55 additions & 35 deletions docs/commands.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
<!-- Generated from src/config/commands.ts by `bun run generate:commands`. Do not edit manually. -->

# Commands

Commands are typed at the start of a message using the `/` prefix. Press `/` to open the command suggestion menu, then Tab to complete or Enter to select.
Commands are written as whole-word `/` triggers anywhere in your message. Press `/` to open the command suggestion menu, then Tab to complete or Enter to select.

Commands can be combined when their behavior allows it. For example, `/screen /think` captures the screen and enables extended reasoning, while `/think /tldr` summarizes with thinking enabled.

Commands that operate on text follow a consistent input priority:

1. **Highlighted text + no typed text:** highlighted text is the input
2. **No highlighted text + typed text after command:** typed text is the input
3. **Both present:** highlighted text is the primary input; typed text is appended as an additional instruction

This means you can highlight a paragraph anywhere on screen, summon Thuki with double-tap Control, type a command, and hit Enter without typing anything else.
This means you can highlight text anywhere on screen, summon Thuki with double-tap Control, type a command, and hit Enter without retyping the selected content.

## /search

Runs agentic web search and answers from live sources with citations.

**Usage:** `/search <question>`

**Examples:**
- `/search who owns Figma now?`: searches live sources for a current answer
- `/search latest React 19 release notes`: retrieves recent release information from the web

**Behavior:** Routes the message through Thuki's local search pipeline instead of plain chat. Answers are grounded in retrieved web sources and typically include inline citations plus a Sources footer.

**Limit:** Requires the search sandbox to be running. Use it for current, changing, or cutoff-sensitive information.

---

Expand All @@ -20,74 +38,76 @@ Captures your screen and attaches it as context for the current message.

**Examples:**
- `/screen`: sends a screenshot with no additional message
- `/screen what is this error?`: attaches a screenshot and asks the question
- `/screen what is this error?`: attaches a screenshot and asks a question about it

**Behavior:** The screenshot is taken the moment you press Enter. Thuki's own window is excluded from the capture: no flicker, no hide. The image appears in your message bubble exactly like a pasted screenshot.
**Behavior:** The screenshot is taken when you submit the message. Thuki's own window is excluded from the capture, and the image appears in your message bubble like a pasted screenshot.

**Composable:** `/screen` works with all other commands. `/screen /rewrite` captures the screen and rewrites whatever text the model sees. `/screen /think` enables extended reasoning on the captured content.
**Composable:** `/screen` can combine with `/think` and utility commands. For example, `/screen /rewrite` captures the screen and rewrites whatever text the model can see.

**Limit:** One `/screen` capture per message. You may also attach up to 3 images manually (paste, drag, or the camera button) for a total of 4 images per message.
**Limit:** One `/screen` capture per message. You may also attach up to 3 images manually for a total of 4 images per message.

**Permission:** Requires Screen Recording permission. On first use, macOS will prompt you to grant it. If denied, Thuki cannot capture the screen. Grant access in System Settings > Privacy & Security > Screen Recording.
**Permission:** Requires Screen Recording permission. If denied, Thuki cannot capture the screen until access is granted in System Settings.

---

## /think

Enables extended reasoning before the model responds. The model works through the problem step by step internally before writing its answer.
Enables extended reasoning before the model responds.

**Usage:** `/think [optional message or highlighted text]`

**Examples:**
- `/think` (with highlighted text): reasons through the selected content
- `/think` with highlighted text: reasons through the selected content
- `/think what are the tradeoffs of a monorepo vs polyrepo?`: asks a question with deep reasoning enabled

**Behavior:** A collapsible "Thinking" block appears above the response showing the model's reasoning chain. The final answer appears below it as normal.
**Behavior:** A collapsible Thinking block appears above the response showing the model's reasoning chain. The final answer appears below it as normal.

**Composable:** `/think` works with all utility commands. `/think /tldr` summarizes with extended reasoning enabled.
**Composable:** `/think` works with `/screen` and all utility commands. For example, `/think /tldr` summarizes with extended reasoning enabled.

---

## /translate

Translates text to another language.

**Usage:** `/translate [language] [text]` or `/translate` with highlighted text
**Usage:** `/translate [language] [text] or /translate with highlighted text`

**Examples:**
- `/translate` (with highlighted text): auto-detects language and translates. Non-English input translates to English; English input translates to Vietnamese
- `/translate ja` (with highlighted text): translates highlighted text to Japanese
- `/translate Spanish meeting notes here`: translates the typed text to Spanish
- `/translate` with highlighted text: auto-detects the source language and translates it
- `/translate ja` with highlighted text: translates highlighted text to Japanese
- `/translate Spanish meeting notes here`: translates typed text to Spanish

**Behavior:** Outputs only the translation with no commentary or explanation.

**Language format:** You can specify the target language by full name (`French`), ISO code (`fr`, `fra`), or common shorthand. The model interprets it flexibly.
**Language format:** The target language can be a full name (`French`), ISO code (`fr`, `fra`), or common shorthand.

**Default behavior:** If no language is specified, non-English input is translated to English and English input is translated to Vietnamese.
**Default behavior:** If no language is specified, non-English input translates to English and English input translates to Vietnamese.

---

## /rewrite

Rewrites text to read more naturally and clearly.

**Usage:** `/rewrite [text]` or `/rewrite` with highlighted text
**Usage:** `/rewrite [text] or /rewrite with highlighted text`

**Examples:**
- `/rewrite` (with highlighted text): rewrites the selected text
- `/rewrite so basically what happened was i was trying to fix the bug`: rewrites the typed text
- `/rewrite` with highlighted text: rewrites the selected text
- `/rewrite so basically what happened was i was trying to fix the bug`: rewrites typed text for clarity

**Behavior:** Preserves the original meaning while improving flow and readability. Output only: no commentary or explanation.
**Behavior:** Preserves the original meaning while improving flow and readability. Outputs only the rewritten text.

---

## /tldr

Summarizes text into 1-3 short, direct sentences.

**Usage:** `/tldr [text]` or `/tldr` with highlighted text
**Usage:** `/tldr [text] or /tldr with highlighted text`

**Examples:**
- `/tldr` (with highlighted text): summarizes the selected content
- `/tldr [paste a long article]`: summarizes the typed or pasted text
- `/tldr` with highlighted text: summarizes the selected content
- `/tldr [paste a long article]`: summarizes typed or pasted text

**Behavior:** Captures the core message, key decision, or critical takeaway. Skips background detail and qualifications.

Expand All @@ -97,11 +117,11 @@ Summarizes text into 1-3 short, direct sentences.

Fixes grammar, spelling, and punctuation while preserving your voice.

**Usage:** `/refine [text]` or `/refine` with highlighted text
**Usage:** `/refine [text] or /refine with highlighted text`

**Examples:**
- `/refine` (with highlighted text): corrects the selected text
- `/refine hey just wanted to follow up on the thing we discussed`: cleans up the typed text
- `/refine` with highlighted text: corrects the selected text
- `/refine hey just wanted to follow up on the thing we discussed`: cleans up typed text

**Behavior:** Corrects errors and smooths rough phrasing without restructuring or adding new ideas. Your original tone and meaning stay intact.

Expand All @@ -111,24 +131,24 @@ Fixes grammar, spelling, and punctuation while preserving your voice.

Extracts key points from text as a markdown bullet list.

**Usage:** `/bullets [text]` or `/bullets` with highlighted text
**Usage:** `/bullets [text] or /bullets with highlighted text`

**Examples:**
- `/bullets` (with highlighted text): extracts key points from the selection
- `/bullets [paste meeting notes]`: extracts key points from the typed or pasted content
- `/bullets` with highlighted text: extracts key points from the selection
- `/bullets [paste meeting notes]`: extracts key points from typed or pasted content

**Behavior:** Each point is a concise, self-contained statement. Ordered by importance or logical sequence. Filler and repetition are removed. Output is a `- ` prefixed markdown list.
**Behavior:** Each point is a concise, self-contained statement. Ordered by importance or logical sequence. Filler and repetition are removed. Output uses `- ` prefixed markdown bullets.

---

## /todos

Summarizes what a piece of text is about, then extracts every task, action item, and commitment as a markdown checkbox list.

**Usage:** `/todos [text]` or `/todos` with highlighted text
**Usage:** `/todos [text] or /todos with highlighted text`

**Examples:**
- `/todos` (with highlighted text): summarizes and extracts to-dos from the selected text
- `/todos [paste a conversation or notes]`: processes the typed or pasted content
- `/todos` with highlighted text: summarizes and extracts to-dos from the selected text
- `/todos [paste a conversation or notes]`: processes typed or pasted content

**Behavior:** Responds in two parts: a short paragraph explaining the context and what is at stake, followed by a `- [ ]` checkbox list of all tasks. Each to-do includes who is responsible (if mentioned) and any deadline or timeframe. Observations and background that imply no action are excluded.
**Behavior:** Responds in two parts: a short paragraph explaining the context and what is at stake, followed by a `- [ ]` checkbox list of all tasks. Each to-do includes who is responsible, plus any deadline or timeframe if mentioned.
2 changes: 1 addition & 1 deletion docs/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Controls the system prompt prepended to every conversation sent to Ollama.

| Variable | Description | Default |
| :--- | :--- | :--- |
| `THUKI_SYSTEM_PROMPT` | Custom system prompt for all conversations. If unset or empty, the built-in default is used. | Built-in secretary persona prompt (see `src-tauri/src/commands.rs`) |
| `THUKI_SYSTEM_PROMPT` | Custom base system prompt for all conversations. If unset or empty, the built-in default is used. Thuki still appends its generated slash-command appendix so built-in command knowledge stays available. | Built-in secretary persona prompt plus generated slash-command appendix (see `src-tauri/src/commands.rs`) |

### Model Configuration

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"scripts": {
"dev": "tauri dev",
"frontend:dev": "vite",
"generate:commands": "bun scripts/generate-commands.ts",
"build:frontend": "tsc && vite build",
"build:backend": "tauri build --bundles app",
"build:all": "bun run build:frontend && bun run build:backend",
Expand Down
30 changes: 30 additions & 0 deletions scripts/generate-commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import {
renderCommandsMarkdown,
renderSlashCommandPromptAppendix,
} from '../src/config/commandArtifacts';

const repoRoot = fileURLToPath(new URL('../', import.meta.url));
const outputs = [
{
path: resolve(repoRoot, 'docs/commands.md'),
content: renderCommandsMarkdown(),
},
{
path: resolve(
repoRoot,
'src-tauri/prompts/generated/slash_commands.txt',
),
content: renderSlashCommandPromptAppendix(),
},
];

await Promise.all(
outputs.map(async ({ path, content }) => {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, content, 'utf8');
}),
);
23 changes: 23 additions & 0 deletions src-tauri/prompts/generated/slash_commands.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Supported slash commands

These are Thuki's only built-in slash commands: /search, /screen, /think, /translate, /rewrite, /tldr, /refine, /bullets, /todos.

If the user asks what slash commands are available, what built-in commands exist, or how to use them, answer with the slash-command list below. Do not answer about generic tools, tool availability, or function calling.

/search: agentic web search for current or cutoff-sensitive questions.

/screen: capture current screen and attach it as image context.

/think: enable extended reasoning before answering.

/translate: translate selected or typed text to requested language.

/rewrite: rewrite text for clarity and flow.

/tldr: summarize text in 1-3 short direct sentences.

/refine: fix grammar, spelling, punctuation, and rough phrasing while preserving tone.

/bullets: extract key points as markdown bullets.

/todos: summarize context and extract tasks as markdown checkboxes.
48 changes: 43 additions & 5 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use tokio_util::sync::CancellationToken;
pub const DEFAULT_OLLAMA_URL: &str = "http://127.0.0.1:11434";
pub const DEFAULT_MODEL_NAME: &str = "gemma4:e2b";
const DEFAULT_SYSTEM_PROMPT: &str = include_str!("../prompts/system_prompt.txt");
const SLASH_COMMAND_PROMPT_APPENDIX: &str = include_str!("../prompts/generated/slash_commands.txt");

/// Classifies the kind of error returned from the Ollama backend.
/// Used by the frontend to pick accent bar color and display copy.
Expand Down Expand Up @@ -191,11 +192,33 @@ pub struct SystemPrompt(pub String);

/// Reads `THUKI_SYSTEM_PROMPT` from the environment, falling back to the
/// built-in default when unset or empty.
pub fn compose_system_prompt_with_appendix(base_prompt: &str, appendix: &str) -> String {
let base = base_prompt.trim_end();
let appendix = appendix.trim();

if appendix.is_empty() {
base.to_string()
} else {
format!("{base}\n\n{appendix}")
}
}

/// Reads `THUKI_SYSTEM_PROMPT` from the environment, falling back to the
/// built-in default when unset or empty.
pub fn compose_system_prompt(base_prompt: &str) -> String {
compose_system_prompt_with_appendix(base_prompt, SLASH_COMMAND_PROMPT_APPENDIX)
}

/// Reads `THUKI_SYSTEM_PROMPT` from the environment as the base prompt, then
/// appends the generated slash-command appendix so built-in command knowledge
/// stays in sync even when the persona prompt is overridden.
pub fn load_system_prompt() -> String {
std::env::var("THUKI_SYSTEM_PROMPT")
let base_prompt = std::env::var("THUKI_SYSTEM_PROMPT")
.ok()
.filter(|s| !s.trim().is_empty())
.unwrap_or_else(|| DEFAULT_SYSTEM_PROMPT.to_string())
.unwrap_or_else(|| DEFAULT_SYSTEM_PROMPT.to_string());

compose_system_prompt(&base_prompt)
}

/// Model configuration loaded once at startup from the `THUKI_SUPPORTED_AI_MODELS`
Expand Down Expand Up @@ -1253,7 +1276,7 @@ mod tests {
std::env::remove_var("THUKI_SYSTEM_PROMPT");

let prompt = load_system_prompt();
assert_eq!(prompt, DEFAULT_SYSTEM_PROMPT);
assert_eq!(prompt, compose_system_prompt(DEFAULT_SYSTEM_PROMPT));
}

#[test]
Expand All @@ -1262,7 +1285,7 @@ mod tests {
std::env::set_var("THUKI_SYSTEM_PROMPT", "Custom prompt");

let prompt = load_system_prompt();
assert_eq!(prompt, "Custom prompt");
assert_eq!(prompt, compose_system_prompt("Custom prompt"));

std::env::remove_var("THUKI_SYSTEM_PROMPT");
}
Expand All @@ -1273,11 +1296,26 @@ mod tests {
std::env::set_var("THUKI_SYSTEM_PROMPT", " ");

let prompt = load_system_prompt();
assert_eq!(prompt, DEFAULT_SYSTEM_PROMPT);
assert_eq!(prompt, compose_system_prompt(DEFAULT_SYSTEM_PROMPT));

std::env::remove_var("THUKI_SYSTEM_PROMPT");
}

#[test]
fn compose_system_prompt_appends_slash_command_appendix() {
let prompt = compose_system_prompt("Base prompt");

assert!(prompt.starts_with("Base prompt\n\n# Supported slash commands"));
assert!(prompt.contains("/search:"));
}

#[test]
fn compose_system_prompt_returns_base_when_appendix_is_blank() {
let prompt = compose_system_prompt_with_appendix("Base prompt", " ");

assert_eq!(prompt, "Base prompt");
}

#[test]
fn conversation_history_new_starts_at_epoch_zero() {
let h = ConversationHistory::new();
Expand Down
Loading