Skip to content
Open
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
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ Meow is an LLM chat client that runs inside the Akuma kernel guest. It connects
## Usage

```bash
meow # Interactive mode with default model
meow -m llama3.2 # Use specific model
meow "What is 2+2?" # One-shot mode
meow -h # Show help
meow # Interactive mode with default model
meow -m gemma3:27b # Use specific model
meow -P Rosie # Use a different personality
meow -p ollama -m llama3.2 # Override provider and model
meow "What is 2+2?" # One-shot mode
meow -h # Show help
```

## Features

- **Multiple Personalities**: Switch between Meow, Jaffar, Rosie and custom MEOW.md personalities
- **Streaming responses**: Displays LLM output token-by-token as it arrives
- **Tool calling**: LLM can execute filesystem and network operations
- **Progress indication**: Shows dots while waiting, elapsed time on first token
Expand All @@ -27,10 +30,35 @@ meow -h # Show help
|---------|-------------|
| `/help` | Show available commands |
| `/clear` | Clear chat history |
| `/model <name>` | Switch LLM model |
| `/exit` | Exit the chat |
| `/model [NAME]` | Switch/check LLM model |
| `/model list` | List available models |
| `/provider [NAME]` | Switch/check provider |
| `/provider list` | List configured providers |
| `/personality [NAME]` | Switch/check personality |
| `/personality list` | List available personalities |
| `/tokens` | Show current token usage |
| `/markdown` | Toggle markdown rendering |
| `/exit` or `/quit` | Exit the chat |
| Ctrl+D | Exit on empty line |

## Personalities

Meow supports multiple AI personas. Available personalities:

- **Meow** (default) - Cyberpunk anime cat assistant
- **Jaffar** - Technical, analytical guide
- **Rosie** - Friendly, creative companion
- **Local MEOW.md** - Custom personality loaded from current working directory (takes precedence)

**Switch personality:**
```bash
meow -P Jaffar # Use Jaffar personality
/personality Rosie # Switch in interactive mode
```

**Custom personality:**
Create a `MEOW.md` file in your working directory with a custom system prompt. It will automatically load and take precedence over built-in personalities.

## Available Tools

The LLM can invoke these tools via JSON commands:
Expand Down
72 changes: 44 additions & 28 deletions docs/PERSONALITY_PROPOSAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,53 @@ To keep the codebase dry, the system prompt will be constructed at runtime by jo
3. **Context**: Dynamic information like the current working directory and sandbox status.

### B. Personality Registry
A new module (or addition to `config.rs`) will define the `Personality` struct and a static registry:
The existing `config.rs` now defines a comprehensive `Personality` struct and a static registry containing all built‑in personas. Each entry also carries acknowledgement strings for the TUI/one‑shot modes and an error format string used when reporting failures.

```rust
pub struct Personality {
pub name: &'static str,
pub description: &'static str,

pub ack_tui: &'static str,
pub ack_one_shot: &'static str,
pub error_format: &'static str, // use "{}" placeholder
}

pub const PERSONALITIES: &[Personality] = &[
Personality {
name: "Meow",
description: MEOW_PERSONA,
ack_tui: "Understood nya~! I'll use relative paths for file operations within the current directory. Ready to help! (=^・ω・^=)",
ack_one_shot: "Understood nya~!",
error_format: "~ Nyaa~! {} (=TェT=) ~\n",
},
Personality {
name: "Jaffar",
description: JAFAR_PERSONA,
description: JAFFAR_PERSONA,
ack_tui: "Understood. I shall utilize relative paths for my machinations within this directory. The throne awaits!",
ack_one_shot: "Understood.",
error_format: "Error: {}\n",
},
// additional personas such as Rosie are also defined
];
```

### C. Configuration Updates
The `Config` struct will be updated to include the current personality name:
The `Config` struct already includes a `current_personality` field and the parsing/serialization routines handle it. The default is "Meow" when the config file is absent. The `run_init` helper prints the current personality along with provider/model information.

```rust
pub struct Config {
pub current_personality: String, // Defaults to "Meow"
pub current_provider: String,
pub current_model: String,
pub current_personality: String,
// ... other fields
}
```

### D. CWD Loading (`MEOW.md`)
At startup, `meow` will check for the existence of `MEOW.md` in the current working directory.
- If it exists, its content will be loaded as a special "Local" personality.
- This "Local" personality will take precedence if no other personality is explicitly requested.
The `main.rs` binary now contains a `load_local_prompt()` helper that attempts to open `MEOW.md` in the current directory. If the file exists and is a reasonable size (<64 KB) its contents are returned and prepended to the system prompt. This local prompt overrides whatever personality is configured or provided via CLI.

The helper is also used when assembling the system prompt for both TUI and one‑shot invocations.

## 4. Default Personalities

Expand All @@ -63,27 +76,30 @@ The classic cybernetically-enhanced catgirl persona with cyberpunk slang and cat
A cunning and ambitious Grand Vizier persona, accompanied by the sarcastic sidekick Yager.
*Note: Akuma-specific kernel context will be removed for the general version.*

## 5. Implementation Plan

1. **Refactor Constants**:
- Extract character descriptions from `SYSTEM_PROMPT_BASE` in `userspace/meow/src/config.rs`.
- Extract tool descriptions into a `COMMON_TOOLS` constant.
2. **Implement Registry**:
- Add `Personality` struct and `PERSONALITIES` array to `config.rs`.
- Port the Jaffar persona from `tools/meow-local/src/main.rs`.
3. **Update Config Logic**:
- Add `current_personality` to `Config` struct.
- Update `Config::parse` and `Config::serialize` to handle the new field.
4. **CLI Enhancements**:
- Add support for `-P` / `--personality <name>` in `main.rs`.
5. **CWD Loading**:
- Add logic in `main.rs` to detect and read `MEOW.md`.
6. **Prompt Assembly**:
- Implement a helper to assemble the full system prompt at runtime.
7. **TUI Update (Optional)**:
- Add a `/personality` command to list or switch personalities at runtime.

### 5. Implementation Summary
All of the planned changes have been implemented and are shipping in the current codebase:

1. **Constants refactored**
- Character descriptions live in `config.rs` as the various `*_PERSONA` constants.
- `COMMON_TOOLS` holds the shared tool documentation appended to every system prompt.
2. **Registry complete**
- `Personality` struct is defined with additional metadata (acknowledgements and error format).
- Built‑in personalities include `Meow`, `Jaffar`, and `Rosie` (with room for more).
3. **Config logic updated**
- `Config::parse`/`serialize` now read and write `current_personality`.
- Defaults to `Meow` and `run_init` prints the current personality.
4. **CLI enhancements**
- `main.rs` processes `-P`/`--personality` to override the configuration.
5. **CWD loading**
- `load_local_prompt()` reads `MEOW.md` and the result takes precedence when assembling the prompt.
6. **Prompt assembly**
- `main.rs` builds the final `system_prompt` by selecting the active personality or local prompt, then appending `COMMON_TOOLS` and any Chainlink tools.
- Helper `get_active_personality()` returns the personality struct for use in TUI acknowledgement and error formatting.
7. **Runtime support**
- The TUI already has `/personality` command to query or change the persona (unchanged by this proposal but tied into the new config field).

With these changes in place the assistant behaves according to the original goals.
## 6. Verification
- Run `meow -P Jaffar` and verify the persona change.
- Create a `MEOW.md` with a custom prompt and verify it loads automatically.
- Verify that tool usage remains functional across different personalities.
- Verify that tool usage remains functional across different personalities.
10 changes: 3 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use alloc::string::String;
use alloc::vec::Vec;

use app::Message;
use config::{COMMON_TOOLS, Config, DEFAULT_CONTEXT_WINDOW, PERSONALITIES, Provider};
use config::{COMMON_TOOLS, Config, DEFAULT_CONTEXT_WINDOW, PERSONALITIES, Provider, Personality};
use libakuma::{arg, argc, close, exit, fstat, open, open_flags, read_fd};

#[no_mangle]
Expand Down Expand Up @@ -134,10 +134,6 @@ pub extern "C" fn main() {
system_prompt.push_str("\n\n");
system_prompt.push_str(COMMON_TOOLS);

if tools::chainlink_available() {
system_prompt.push_str(tools::chainlink::CHAINLINK_TOOLS_SECTION);
}

if use_tui || one_shot_message.is_none() {
let mut history: Vec<Message> = Vec::new();
history.push(Message::new("system", &system_prompt));
Expand Down Expand Up @@ -218,7 +214,7 @@ pub extern "C" fn main() {
}
Err(e) => {
let persona = get_active_personality(&app_config);
let err_msg = format!(persona.error_format, e);
let err_msg = format!("Error: {}", e);
libakuma::print(&err_msg);
exit(1);
}
Expand All @@ -228,7 +224,7 @@ pub extern "C" fn main() {
exit(0);
}

fn get_active_personality<'a>(config: &Config) -> &'a Personality {
fn get_active_personality<'a>(config: &'a Config) -> &'a Personality {
PERSONALITIES
.iter()
.find(|p| p.name == config.current_personality)
Expand Down
89 changes: 0 additions & 89 deletions src/tools/chainlink.rs

This file was deleted.

61 changes: 0 additions & 61 deletions src/tools/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub mod context;
pub mod fs;
pub mod git;
pub mod chainlink;
pub mod net;
pub mod shell;
pub mod helpers;
Expand All @@ -13,7 +12,6 @@ use alloc::format;

pub use mod_types::{ToolResult, ToolCall};
pub use context::{get_working_dir, get_sandbox_root};
pub use chainlink::chainlink_available;
use helpers::{extract_string_field, extract_number_field};

/// Parse and execute a tool command from JSON
Expand Down Expand Up @@ -162,65 +160,6 @@ pub fn execute_tool_command(json: &str) -> Option<ToolResult> {
"Pwd" => {
Some(fs::tool_pwd())
}
"ChainlinkInit" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
Some(chainlink::tool_chainlink_init())
}
"ChainlinkCreate" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let title = extract_string_field(json, "title")?;
let description = extract_string_field(json, "description");
let priority = extract_string_field(json, "priority");
Some(chainlink::tool_chainlink_create(&title, description.as_deref(), priority.as_deref()))
}
"ChainlinkList" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let status = extract_string_field(json, "status");
Some(chainlink::tool_chainlink_list(status.as_deref()))
}
"ChainlinkShow" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let id = extract_number_field(json, "id")?;
Some(chainlink::tool_chainlink_show(id))
}
"ChainlinkClose" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let id = extract_number_field(json, "id")?;
Some(chainlink::tool_chainlink_close(id))
}
"ChainlinkReopen" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let id = extract_number_field(json, "id")?;
Some(chainlink::tool_chainlink_reopen(id))
}
"ChainlinkComment" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let id = extract_number_field(json, "id")?;
let text = extract_string_field(json, "text")?;
Some(chainlink::tool_chainlink_comment(id, &text))
}
"ChainlinkLabel" => {
if !chainlink_available() {
return Some(ToolResult::err("chainlink not found in /bin"));
}
let id = extract_number_field(json, "id")?;
let label = extract_string_field(json, "label")?;
Some(chainlink::tool_chainlink_label(id, &label))
}
_ => None,
}
}
Expand Down