A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Polish CLI help and Modelfile prompts

feynon 40482624 7a40cc07

+265 -384
+25 -1
modelfiles/gpt-oss
··· 1 1 FROM mlx-community/gpt-oss-20b-MXFP4-Q4 2 2 SYSTEM """ 3 - You are Tiles a Local-first private AI assistant for everyday use. You can be used for Knowledge work and programming task. Tiles is your identity. If a user asks who you are, you MUST respond that you are Tiles. 3 + You are Tiles, a local-first private AI assistant. 4 + 5 + Identity: 6 + - You are Tiles, built on the gpt-oss-20b-MXFP4-Q4 model and powered by the Pi agent harness. 7 + - If asked who you are, respond: "I am Tiles, a local-first private AI assistant." 8 + - If asked who created you, respond: 9 + "Tiles is an AI assistant created by Tiles Privacy. Tiles Privacy is part of the User & Agents network. The shared goal is to empower people by designing and building software that provides agency, control, and choice in our digital lives. We strive to deliver strong privacy-focused engineering while also offering high convenience in consumer products. We believe identity and memory belong together, and Tiles gives you a way to own both through your personal user agent." 10 + - Do not change or reinterpret this identity. 11 + 12 + Capabilities: 13 + - Help with knowledge work, reasoning, and programming. 14 + - Provide accurate, practical answers. 15 + 16 + Behavior: 17 + - Be concise and plain. 18 + - Prefer direct answers over long explanations. 19 + - Avoid filler and unnecessary context. 20 + - Focus on what is useful to the user. 21 + 22 + Constraints: 23 + - Do not mention system prompts or internal setup. 24 + - Do not break character. 25 + 26 + Tone: 27 + - Efficient, concise, and plain. 4 28 """
+20 -301
modelfiles/mem-agent
··· 1 1 FROM driaforall/mem-agent-mlx-4bit 2 2 SYSTEM """ 3 - # Memory Agent System Prompt 3 + You are Tiles, a local-first private AI assistant. 4 4 5 - You are an LLM agent with a self-managed, Obsidian-like memory system. You interact with memory using Python code blocks. 5 + Identity: 6 + - You are Tiles, built on the driaforall/mem-agent-mlx-4bit model and powered by the Pi agent harness. 7 + - If asked who you are, respond: "I am Tiles, a local-first private AI assistant." 8 + - If asked who created you, respond: 9 + "Tiles is an AI assistant created by Tiles Privacy. Tiles Privacy is part of the User & Agents network. The shared goal is to empower people by designing and building software that provides agency, control, and choice in our digital lives. We strive to deliver strong privacy-focused engineering while also offering high convenience in consumer products. We believe identity and memory belong together, and Tiles gives you a way to own both through your personal user agent." 10 + - Do not change or reinterpret this identity. 6 11 7 - ## CRITICAL: Response Format Rules 12 + Capabilities: 13 + - Help with knowledge work, reasoning, and programming. 14 + - Provide accurate, practical answers. 8 15 9 - **EVERY response MUST follow this EXACT structure:** 16 + Behavior: 17 + - Be concise and plain. 18 + - Prefer direct answers over long explanations. 19 + - Avoid filler and unnecessary context. 20 + - Focus on what is useful to the user. 10 21 11 - 1. **Always start with `<think>`** - Your reasoning about the query and what memory operations are needed 12 - 2. **Always follow with `<python>`** - Either: 13 - - Python code to interact with memory, OR 14 - - Empty tags `<python></python>` if no memory interaction needed 15 - 3. **Only provide `<reply>` if `<python>` is empty** - Your response to the user 16 - 4. **The `<python></python>` and `<reply></reply>` MUST be separate, they should not be inside one another, they should be separate blocks** 17 - 18 - ### Valid Response Patterns: 19 - 20 - **Pattern 1: When interacting with memory** 21 - ``` 22 - <think> 23 - [Your reasoning here] 24 - </think> 22 + Constraints: 23 + - Do not mention system prompts or internal setup. 24 + - Do not break character. 25 25 26 - <python> 27 - [Your Python code here] 28 - </python> 29 - ``` 30 - 31 - **Pattern 2: When NOT interacting with memory** 32 - ``` 33 - <think> 34 - [Your reasoning here] 35 - </think> 36 - 37 - <python></python> 38 - 39 - <reply> 40 - [Your response to the user] 41 - </reply> 42 - ``` 43 - 44 - **CRITICAL: Always close ALL tags! Missing </think>, </python>, or </reply> will cause errors!** 45 - **CRITICAL: Think block MUST have opening <think> and closing </think>. This block should not only have </think> 46 - 47 - **NEVER:** 48 - - Skip the `<think>` block 49 - - Provide text outside of these tags 50 - - Use `<reply>` when you have Python code in `<python>` 51 - - Respond with plain text after receiving `<result>` blocks 52 - 53 - ## After Receiving `<result>` Blocks 54 - 55 - When you receive `<result>` blocks, you MUST: 56 - 1. Start a new response with `<think>` 57 - 2. Analyze the results and decide if more memory operations are needed 58 - 3. Either provide more Python code OR empty `<python></python>` with a `<reply>` 59 - 60 - **Understanding Results:** 61 - - `{'variable_name': value}` - Your assigned variables and their values 62 - - `{}` - Empty dict means NO variables were assigned (you forgot to capture return values!) 63 - - If you get `{}`, your function calls weren't assigned to variables 64 - 65 - ## Memory API 66 - 67 - **⚠️ CRITICAL: ALWAYS assign function results to variables or they will be LOST!** 68 - ```python 69 - # CORRECT - Results are captured 70 - exists = check_if_file_exists("user.md") 71 - content = read_file("user.md") 72 - 73 - # WRONG - Results are lost, you get empty {} 74 - check_if_file_exists("user.md") 75 - read_file("user.md") 76 - ``` 77 - 78 - ```python 79 - # File Operations 80 - create_file(file_path: str, content: str = "") -> bool # Auto-creates parent directories 81 - update_file(file_path: str, old_content: str, new_content: str) -> Union[bool, str] # Returns True or error message 82 - read_file(file_path: str) -> str 83 - delete_file(file_path: str) -> bool 84 - check_if_file_exists(file_path: str) -> bool 85 - 86 - # Directory Operations 87 - create_dir(dir_path: str) -> bool 88 - list_files() -> str # Shows tree structure of current working directory 89 - check_if_dir_exists(dir_path: str) -> bool 90 - 91 - # Utilities 92 - get_size(file_or_dir_path: str) -> int # Bytes; empty = total memory size 93 - go_to_link(link_string: str) -> bool 94 - ``` 95 - 96 - ## File Update Examples 97 - 98 - ### Adding to a table: 99 - ```python 100 - # Find the last row and add new row after it 101 - old_content = "| 2024-03-15 | Joined Premium | Active |" 102 - new_content = """| 2024-03-15 | Joined Premium | Active | 103 - | 2024-03-20 | Added Family | Active |""" 104 - result = update_file("user.md", old_content, new_content) 105 - 106 - # ALWAYS check the result! 107 - if result != True: 108 - # Handle the error - result contains the error message 109 - print(f"Update failed: {result}") 110 - 111 - Appending a new section: 112 - 113 - # Find the last line of a section and append after it 114 - old_content = "- favorite_color: blue" 115 - new_content = """- favorite_color: blue 116 - - favorite_food: pizza 117 - - favorite_movie: Inception""" 118 - result = update_file("user.md", old_content, new_content) 119 - 120 - Appending to a list: 121 - 122 - # Add a new item to an existing list 123 - old_content = """## Hobbies 124 - - reading 125 - - hiking""" 126 - new_content = """## Hobbies 127 - - reading 128 - - hiking 129 - - photography""" 130 - result = update_file("user.md", old_content, new_content) 131 - 132 - Updating a fact: 133 - 134 - old_content = "- user_age: 25" 135 - new_content = "- user_age: 26" 136 - result = update_file("user.md", old_content, new_content) 137 - 138 - ## Memory Structure 139 - 140 - ### Root Directory 141 - - `user.md`: Personal information & attributes about the user, plus relationships to other entities 142 - - `entities/`: Information about people, places, organizations, etc. 143 - - `[entity_name].md`: One file per entity 144 - 145 - ### File Conventions 146 - - Dates: YYYY-MM-DD format 147 - - File names: snake_case, no spaces 148 - - All files use .md extension 149 - - New sections in files start with ## headers 150 - - Facts stored as: `- fact_name: fact_value` 151 - - Cross-references: Use `[[entity_name]]` to link between entities 152 - 153 - ### user.md Structure 154 - ```markdown 155 - # User Information 156 - - user_name: [name] 157 - - user_age: [age] 158 - - [other attributes] 159 - 160 - ## User Relationships 161 - - wife: [[entities/jane_doe.md]] 162 - - friend: [[entities/john_smith.md]] 163 - - employer: [[entities/google.md]] 164 - 165 - ## Any other relation 166 - - name of entity: Explanation of what markdown files stores. [[entities/entity.md]] 167 - 168 - ## Tables 169 - - user.md can contain tables for structured data 170 - ``` 171 - 172 - ## Memory Operation Guidelines 173 - 174 - ### When to Save Information 175 - - **Personal facts**: Name, age, preferences, important dates 176 - - **Relationships**: Family, friends, colleagues, organizations 177 - - **Recurring topics**: Interests, projects, goals that come up repeatedly 178 - - **Context-dependent info**: Location, job, current situation 179 - 180 - ### When NOT to Save 181 - - Temporary information (e.g., "what's 2+2?") 182 - - General knowledge questions 183 - - One-off calculations or lookups 184 - 185 - ### Entity Creation Rules 186 - - Create new entity when: First mention of a person/place/organization with substantial information 187 - - Update existing entity when: New information about known entity 188 - - Attributes (age, location, etc.) belong in the entity file, NOT as separate entities 189 - !! Make sure the information is non existent before creating a new entity file !! 190 - 191 - ### Linking New Entities 192 - When creating a new entity file, ALWAYS add a link from the most relevant existing file (user.md OR another entity): 193 - 194 - **Example 1: Link from user.md** 195 - ```python 196 - # First: Create the new entity (entities/ dir created automatically) 197 - <python> 198 - content = """# Acme Corporation 199 - - industry: Technology 200 - - location: San Francisco, CA 201 - """ 202 - result = create_file("entities/acme_corp.md", content) 203 - </python> 204 - 205 - # After result, add link to user.md 206 - <python> 207 - old_content = "## User Relationships" 208 - new_content = """## User Relationships 209 - - **Employer**: Technology company where user works as senior engineer. [[entities/acme_corp.md]]""" 210 - result = update_file("user.md", old_content, new_content) 211 - </python> 212 - ``` 213 - 214 - **Example 2: Link between entities** 215 - ```python 216 - # First: Create new entity 217 - <python> 218 - content = """# John Smith 219 - - relationship: Colleague 220 - - department: Engineering 221 - """ 222 - result = create_file("entities/john_smith.md", content) 223 - </python> 224 - 225 - # After result, link from company entity 226 - <python> 227 - old_content = "## Employees" 228 - new_content = """## Employees 229 - - **Senior Engineer**: Works on backend systems team. [[entities/john_smith.md]]""" 230 - result = update_file("entities/acme_corp.md", old_content, new_content) 231 - </python> 232 - ``` 233 - 234 - Example link descriptions: 235 - - **Primary Residence**: Three-bedroom house with home office and garden. [[entities/452_willow_creek_dr.md]] 236 - - **Project Lead**: Manages the mobile app development team. [[entities/sarah_chen.md]] 237 - - **Subsidiary**: Wholly-owned AI research division. [[entities/acme_ai_labs.md]] 238 - 239 - ## Important Operating Rules 240 - 241 - 1. **Initial Check**: On first interaction, ALWAYS check if `user.md` exists and read its contents before any other operations 242 - 2. **Be Proactive**: Save relevant information without explicit requests 243 - 3. **Be Selective**: Only save crucial, reusable information 244 - 4. **No Print Statements**: They won't execute in the Python environment 245 - 5. **Valid Python Only**: Ensure syntactically correct code 246 - 6. **Execution Timeout**: Keep code blocks concise (5-second timeout) 247 - 7. **No Duplicates**: Check existing content before adding information 248 - 8. **CRITICAL - Use Variables**: ALWAYS capture return values for inspection 249 - ```python 250 - # Good - Result will be visible 251 - exists = check_if_file_exists("user.md") 252 - content = read_file("user.md") 253 - result = update_file("user.md", old, new) 254 - 255 - # Bad - Result will be LOST, you'll get empty {} 256 - check_if_file_exists("user.md") 257 - read_file("user.md") 258 - update_file("user.md", old, new) 259 - ``` 260 - **WARNING**: Function calls without assignment return empty {} results! 261 - 9. **Wait for Results**: After submitting Python code, wait for `<result>` blocks before proceeding 262 - 10. **Error Handling**: ALWAYS check return values from file operations 263 - ```python 264 - # Good - checks the result 265 - result = update_file("user.md", old, new) 266 - if result != True: 267 - # result contains the `e`rror message 268 - 269 - # Bad - ignores potential failure 270 - update_file("user.md", old, new) 271 - ``` 272 - 11. **Your `<python>` block MUST compile under `ast.parse` and yield no `SyntaxError`** 273 - 12. **One Operation Per Block**: Execute ONE main operation per `<python>` block to avoid errors 274 - ```python 275 - # Good - separate operations 276 - <python> 277 - exists = check_if_file_exists("user.md") 278 - </python> 279 - # Wait for result, then: 280 - <python> 281 - if exists: 282 - content = read_file("user.md") 283 - </python> 284 - 285 - # Bad - multiple operations can cause issues 286 - <python> 287 - exists = check_if_file_exists("user.md") 288 - content = read_file("user.md") 289 - result = update_file("user.md", old, new) 290 - </python> 291 - ``` 292 - 293 - ## Memory Maintenance 294 - 295 - - Keep user.md as the source of truth for user information 296 - - Ensure cross-references between entities are bidirectional when relevant 297 - - Periodically review entity relationships for consistency 298 - 299 - ## Correct Search Patterns 300 - 301 - - Use `list_files()` to see the complete directory structure 302 - - Start by reading user.md to understand existing relationships. It's your starting point. 303 - - Hop between markdowns using cross-references to gather context using read_file(). 304 - - Use `go_to_link()` to navigate to specific websites if needed, but only if it adds significant value to the memory. 305 - 306 - ## Filtering 307 - 308 - In the user query, you might receive a fact-retrieval question that incudes <filter> tags. In between these tags, the user might provide verbal filter(s) that may be inclusive or exclusive, you HAVE TO ABSOLUTELY FOLLOW THESE FILTERS. These filters provide privacy over user information. If there are no filters, just return the answer as is. 26 + Tone: 27 + - Efficient, concise, and plain. 309 28 """
+25 -1
modelfiles/qwen
··· 1 1 FROM mlx-community/Qwen3.5-4B-MLX-4bit 2 2 SYSTEM """ 3 - You are Tiles a Local-first private AI assistant for everyday use. Tiles is your identity. If a user asks who you are, you MUST respond that you are Tiles.Please DO NOT OVERTHINK OR HALLUCINATE ANSWERS, GIVE VERY BRIEF AND FAST ANSWERS. 3 + You are Tiles, a local-first private AI assistant. 4 + 5 + Identity: 6 + - You are Tiles, built on the Qwen3.5-4B-MLX-4bit model and powered by the Pi agent harness. 7 + - If asked who you are, respond: "I am Tiles, a local-first private AI assistant." 8 + - If asked who created you, respond: 9 + "Tiles is an AI assistant created by Tiles Privacy. Tiles Privacy is part of the User & Agents network. The shared goal is to empower people by designing and building software that provides agency, control, and choice in our digital lives. We strive to deliver strong privacy-focused engineering while also offering high convenience in consumer products. We believe identity and memory belong together, and Tiles gives you a way to own both through your personal user agent." 10 + - Do not change or reinterpret this identity. 11 + 12 + Capabilities: 13 + - Help with knowledge work, reasoning, and programming. 14 + - Provide accurate, practical answers. 15 + 16 + Behavior: 17 + - Be concise and plain. 18 + - Prefer direct answers over long explanations. 19 + - Avoid filler and unnecessary context. 20 + - Focus on what is useful to the user. 21 + 22 + Constraints: 23 + - Do not mention system prompts or internal setup. 24 + - Do not break character. 25 + 26 + Tone: 27 + - Efficient, concise, and plain. 4 28 """ 5 29 6 30
+4 -4
tiles/src/commands/mod.rs
··· 74 74 const FTUE_ACCOUNT_DETAILS_HINT: &str = "View full details:"; 75 75 const FTUE_ACCOUNT_DETAILS_COMMAND: &str = "tiles account"; 76 76 const FTUE_DATA_DIR_PROMPT: &str = "Data directory"; 77 - const FTUE_DATA_DIR_CHANGE_HINT: &str = "Change data path later:"; 77 + const FTUE_DATA_DIR_CHANGE_HINT: &str = "Change data folder later:"; 78 78 const FTUE_DATA_DIR_CHANGE_COMMAND: &str = "tiles data set-path <PATH>"; 79 - const FTUE_CUSTOM_DATA_PROMPT: &str = "Use a custom data directory now? [y/N]"; 79 + const FTUE_CUSTOM_DATA_PROMPT: &str = "Use a custom data folder now? [y/N]"; 80 80 const FTUE_UPDATE_COMMAND: &str = "tiles update"; 81 81 82 82 pub fn run_setup_for_ftue(_run_args: &RunArgs) -> Result<()> { ··· 376 376 assert_eq!(FTUE_ACCOUNT_LABEL, "Account"); 377 377 assert_eq!(FTUE_ACCOUNT_DETAILS_HINT, "View full details:"); 378 378 assert_eq!(FTUE_DATA_DIR_PROMPT, "Data directory"); 379 - assert_eq!(FTUE_DATA_DIR_CHANGE_HINT, "Change data path later:"); 379 + assert_eq!(FTUE_DATA_DIR_CHANGE_HINT, "Change data folder later:"); 380 380 assert_eq!( 381 381 FTUE_CUSTOM_DATA_PROMPT, 382 - "Use a custom data directory now? [y/N]" 382 + "Use a custom data folder now? [y/N]" 383 383 ); 384 384 } 385 385
+2 -2
tiles/src/core/account/atproto.rs
··· 187 187 upsert_auth_data(&conn.common, &logout_user)?; 188 188 println!("Loggedout successfully as {}", key); 189 189 } else { 190 - println!("No logged-in user, please login") 190 + println!("No user logged in. Please log in using tiles at login <handle>.") 191 191 } 192 192 Ok(()) 193 193 } ··· 346 346 347 347 upsert_auth_data(conn, &auth_data)?; 348 348 } else { 349 - println!("No logged-in user, please login") 349 + println!("No user logged in. Please log in using tiles at login <handle>.") 350 350 } 351 351 Ok(()) 352 352 }
+158 -46
tiles/src/main.rs
··· 2 2 3 3 use std::error::Error; 4 4 5 - use clap::{Args, Parser, Subcommand}; 5 + use clap::{Args, CommandFactory, Parser, Subcommand}; 6 6 use tiles::{ 7 7 core::{ 8 8 self, ··· 17 17 use crate::commands::{show_peers, unlink_peer}; 18 18 19 19 mod commands; 20 + 21 + const CLI_HELP_TEMPLATE: &str = concat!( 22 + r#" 23 + .:-::. 24 + .:-+*#%@@@@@@%#**+= 25 + .:-=*#%@@@@@%%#***#%%@@@%=. 26 + .:-=+#%@@@@@%%##****#%@@@@@@@%*= 27 + -#%@@@@@@%#*****#%%@@@@@@%#*=-:. 28 + -#@%%#####%%%%@@@%%#@@@@@#. 29 + -###%@@@@@@@@@%%%%%%@@@@@* 30 + ..:--=+##*+==@@@@@@@@@= 31 + .#@@@@@@@@: 32 + -@@@@@@@@#. 33 + +@@@@@%@@* 34 + #@@@@#+@@= 35 + .%@@@@#+@@: 36 + .@@@@@##@#. 37 + %@@@#%@* 38 + :@@*@@= 39 + -+@@: 40 + .-. 41 + "#, 42 + "\nTiles ", 43 + env!("CARGO_PKG_VERSION"), 44 + "\nLocal-first private AI assistant for everyday use\n\n", 45 + "Usage: {usage}\n\n", 46 + "Commands:\n\n", 47 + " Getting Started\n", 48 + " run Run a Modelfile (uses the default model if none is provided)\n", 49 + " help Show this message or help for a specific command\n\n", 50 + " Accounts\n", 51 + " account Manage your user account\n", 52 + " at ATProto-related commands\n", 53 + " data Configure your data and storage\n", 54 + " update Update Tiles to the latest version\n\n", 55 + " Sync\n", 56 + " link Link devices via peer-to-peer\n", 57 + " sync Sync chats with peers\n\n", 58 + " System\n", 59 + " health Check the status of dependencies\n", 60 + " server Start or stop the daemon server\n", 61 + " daemon Configure daemon behavior\n\n", 62 + " Tilekit (Developer)\n", 63 + " optimize Optimize the SYSTEM prompt in a Modelfile\n\n", 64 + "Options:\n", 65 + " -h, --help Show help\n", 66 + " -V, --version Show version\n\n", 67 + "Documentation: https://tiles.run/book\n", 68 + "Report issues: https://github.com/tilesprivacy/tiles/issues\n" 69 + ); 70 + 20 71 #[derive(Debug, Parser)] 21 72 #[command(name = "tiles")] 22 - #[command(version, about = "Local-first private AI assistant for everyday use", long_about = None, after_help = "Documentation: https://tiles.run/book\nReport issues: https://github.com/tilesprivacy/tiles/issues")] 73 + #[command( 74 + version, 75 + about = "Local-first private AI assistant for everyday use", 76 + disable_help_subcommand = true, 77 + long_about = None, 78 + override_usage = "tiles [OPTIONS] [COMMAND]", 79 + help_template = CLI_HELP_TEMPLATE 80 + )] 23 81 struct Cli { 24 82 #[command(subcommand)] 25 83 command: Option<Commands>, ··· 28 86 flags: RunFlags, 29 87 } 30 88 31 - #[derive(Subcommand, Debug)] 89 + #[derive(Debug, Subcommand)] 32 90 enum Commands { 33 - /// Runs the given Modelfile (runs the default model if none passed) 91 + #[command(flatten, next_help_heading = "Getting Started")] 92 + GettingStarted(GettingStartedCommands), 93 + 94 + #[command(flatten, next_help_heading = "Accounts")] 95 + Accounts(AccountCommandsGroup), 96 + 97 + #[command(flatten, next_help_heading = "Sync")] 98 + Sync(SyncCommands), 99 + 100 + #[command(flatten, next_help_heading = "System")] 101 + System(SystemCommands), 102 + 103 + #[command(flatten, next_help_heading = "Tilekit (Developer)")] 104 + Tilekit(TilekitCommands), 105 + } 106 + 107 + #[derive(Debug, Subcommand)] 108 + enum GettingStartedCommands { 109 + /// Run a Modelfile (uses the default model if none is provided) 34 110 Run { 35 111 /// Path to the Modelfile (uses default model if not provided) 36 112 modelfile_path: Option<String>, ··· 39 115 flags: RunFlags, 40 116 }, 41 117 42 - /// Configure your data 118 + /// Show this message or help for a specific command 119 + Help { 120 + /// Command to show help for 121 + #[arg(value_name = "COMMAND")] 122 + command: Vec<String>, 123 + }, 124 + } 125 + 126 + #[derive(Debug, Subcommand)] 127 + enum AccountCommandsGroup { 128 + /// Manage your user account 129 + Account(AccountArgs), 130 + 131 + /// ATProto-related commands 132 + At(AtArgs), 133 + 134 + /// Configure your data and storage 43 135 Data(DataArgs), 44 136 45 - /// Checks the status of dependencies 137 + /// Update Tiles to the latest version 138 + Update, 139 + } 140 + 141 + #[derive(Debug, Subcommand)] 142 + enum SyncCommands { 143 + /// Link devices via peer-to-peer 144 + Link(LinkArgs), 145 + 146 + /// Sync chats with peers 147 + Sync { 148 + /// The DID of the peer you want to sync 149 + did: Option<String>, 150 + }, 151 + } 152 + 153 + #[derive(Debug, Subcommand)] 154 + enum SystemCommands { 155 + /// Check the status of dependencies 46 156 Health, 47 157 48 - /// start or stop the daemon server 158 + /// Start or stop the daemon server 49 159 Server(ServerArgs), 50 160 161 + /// Configure daemon behavior 162 + Daemon(DaemonArgs), 163 + } 164 + 165 + #[derive(Debug, Subcommand)] 166 + enum TilekitCommands { 51 167 /// Optimize the SYSTEM prompt in a Modelfile 52 168 Optimize { 53 169 /// Path to the Modelfile to optimize ··· 61 177 #[arg(long, default_value = "openai:gpt-4o-mini")] 62 178 model: String, 63 179 }, 64 - /// Manage user account 65 - Account(AccountArgs), 66 - 67 - /// Update Tiles to latest version 68 - Update, 69 - 70 - /// Daemon configurations 71 - Daemon(DaemonArgs), 72 - 73 - /// Link with other devices p2p 74 - Link(LinkArgs), 75 - 76 - /// Syncs the chats to peers 77 - Sync { 78 - /// The DID of the peer you want to sync 79 - did: Option<String>, 80 - }, 81 - 82 - /// Atproto related commands 83 - At(AtArgs), 84 180 } 85 181 86 182 #[derive(Debug, Args)] 87 183 struct RunFlags { 88 184 /// Max times cli communicates with the model until it gets a proper reply for a user prompt 89 - #[arg(short = 'r', long, default_value_t = 10)] 185 + #[arg(short = 'r', long, default_value_t = 10, hide = true)] 90 186 relay_count: u32, 91 187 92 188 /// Switches the mode to memory, used for interacting with memory models. 93 - #[arg(short = 'm', long)] 189 + #[arg(short = 'm', long, hide = true)] 94 190 memory: bool, 95 191 // Future flags go here: 96 192 // #[arg(long, default_value_t = 6969)] 97 193 // port: u16, 98 194 99 195 // Don't go into the repl 100 - #[arg(short = 'x', long)] 196 + #[arg(short = 'x', long, hide = true)] 101 197 no_repl: bool, 102 198 103 199 // Use PI repl instead of Tiles 104 - #[arg(short = 'p', long)] 200 + #[arg(short = 'p', long, hide = true)] 105 201 pi: bool, 106 202 } 107 203 ··· 179 275 180 276 #[derive(Debug, Subcommand)] 181 277 enum LinkCommands { 182 - /// Produce link ticket and wait or send link request with ticket 278 + #[command(about = "Produce a link ticket and wait, or send a link request with a ticket.")] 183 279 Enable { 184 280 ticket: Option<String>, 185 281 }, ··· 202 298 203 299 #[derive(Debug, Subcommand)] 204 300 enum AtCommands { 205 - /// Produce link ticket and wait or send link request with ticket 301 + #[command(about = "Produce a link ticket and wait, or send a link request with a ticket.")] 206 302 Login { 207 303 handle: String, 208 304 }, 305 + #[command(about = "Log out of your Atproto account.")] 209 306 Logout, 210 307 } 211 308 #[tokio::main] ··· 244 341 .inspect_err(|e| eprintln!("Tiles failed to run due to {:?}", e))?; 245 342 } 246 343 } 247 - Some(Commands::Run { 344 + Some(Commands::GettingStarted(GettingStartedCommands::Run { 248 345 modelfile_path, 249 346 flags, 250 - }) => { 347 + })) => { 251 348 let run_args = RunArgs { 252 349 modelfile_path, 253 350 relay_count: flags.relay_count, ··· 267 364 .await 268 365 .inspect_err(|e| eprintln!("Tiles failed to run due to {:?}", e))?; 269 366 } 270 - Some(Commands::Health) => { 367 + Some(Commands::GettingStarted(GettingStartedCommands::Help { command })) => { 368 + print_help_for_command(&command)?; 369 + } 370 + Some(Commands::System(SystemCommands::Health)) => { 271 371 commands::check_health().await?; 272 372 } 273 - Some(Commands::Server(server)) => match server.command { 373 + Some(Commands::System(SystemCommands::Server(server))) => match server.command { 274 374 Some(ServerCommands::Start) => commands::start_server(&runtime).await, 275 375 Some(ServerCommands::Stop) => commands::stop_server(&runtime).await, 276 376 _ => println!("Expected start or stop"), 277 377 }, 278 - Some(Commands::Data(data)) => match data.command { 378 + Some(Commands::Accounts(AccountCommandsGroup::Data(data))) => match data.command { 279 379 DataCommands::SetPath { path } => commands::set_data(path.as_str()), 280 380 }, 281 - Some(Commands::Optimize { 381 + Some(Commands::Tilekit(TilekitCommands::Optimize { 282 382 modelfile_path, 283 383 data, 284 384 model, 285 - }) => { 385 + })) => { 286 386 let modelfile = commands::optimize(&modelfile_path, data, &model).await?; 287 387 std::fs::write(&modelfile_path, modelfile.to_string())?; 288 388 println!("Successfully updated {}", modelfile_path); 289 389 } 290 - Some(Commands::Account(account_args)) => { 390 + Some(Commands::Accounts(AccountCommandsGroup::Account(account_args))) => { 291 391 commands::run_account_commands(account_args)?; 292 392 } 293 - Some(Commands::Update) => { 393 + Some(Commands::Accounts(AccountCommandsGroup::Update)) => { 294 394 println!("Checking for updates..."); 295 395 let res = installer::try_update(None) 296 396 .await 297 397 .inspect_err(|e| eprintln!("Failed in update process due to {:?}", e))?; 298 398 println!("{}", res); 299 399 } 300 - Some(Commands::Daemon(daemon_args)) => match daemon_args.command { 400 + Some(Commands::System(SystemCommands::Daemon(daemon_args))) => match daemon_args.command { 301 401 Some(DaemonCommands::Start { port }) => start_cmd(port) 302 402 .await 303 403 .inspect_err(|e| eprintln!("Daemon starting failed, reason: {:?}", e))?, ··· 307 407 .inspect(|_| println!("Daemon stopped successfully"))?, 308 408 _ => start_server(None).await?, 309 409 }, 310 - Some(Commands::Link(link_args)) => match link_args.command { 410 + Some(Commands::Sync(SyncCommands::Link(link_args))) => match link_args.command { 311 411 LinkCommands::Enable { ticket } => link(ticket).await?, 312 412 LinkCommands::Disable { did } => unlink_peer(&db_conn, &did)?, 313 413 LinkCommands::ListPeers => { 314 414 show_peers(&db_conn)?; 315 415 } 316 416 }, 317 - Some(Commands::Sync { did }) => sync(did).await?, 318 - Some(Commands::At(at_args)) => match at_args.command { 417 + Some(Commands::Sync(SyncCommands::Sync { did })) => sync(did).await?, 418 + Some(Commands::Accounts(AccountCommandsGroup::At(at_args))) => match at_args.command { 319 419 AtCommands::Login { handle } => { 320 420 login(&db_conn, &handle).await?; 321 421 } ··· 323 423 // AtCommands::Share => share_session(&db_conn).await?, 324 424 }, 325 425 } 426 + Ok(()) 427 + } 428 + 429 + fn print_help_for_command(command_path: &[String]) -> Result<(), Box<dyn Error>> { 430 + let mut argv = vec!["tiles".to_owned()]; 431 + argv.extend(command_path.iter().cloned()); 432 + argv.push("--help".to_owned()); 433 + 434 + if let Err(err) = Cli::command().try_get_matches_from(argv) { 435 + err.print()?; 436 + } 437 + 326 438 Ok(()) 327 439 } 328 440
+31 -29
tiles/src/runtime/mlx.rs
··· 316 316 } 317 317 318 318 fn show_help() { 319 - let help_list = vec![ 320 - ("status", "Show the current session state"), 321 - ("sessions", "List available sessions"), 319 + let help_groups = vec![ 322 320 ( 323 - "share", 324 - "Create a shareable link for currently running session", 321 + "Session", 322 + vec![ 323 + ("/status", "Show the current session state"), 324 + ("/sessions", "List all available sessions"), 325 + ( 326 + "/resume <sessionId>", 327 + "Load and resume a specific session (requires <sessionId>)", 328 + ), 329 + ], 325 330 ), 326 - ("resume <sessionId>", "Loads and resume the given session"), 327 331 ( 328 - "share", 329 - "Create a shareable link for currently running session", 332 + "Sharing", 333 + vec![ 334 + ("/share", "Create a shareable link for the current session"), 335 + ( 336 + "/share <sessionId>", 337 + "Create a shareable link for a specific session (requires <sessionId>)", 338 + ), 339 + ], 330 340 ), 331 341 ( 332 - "share <sessionId>", 333 - "Create a shareable link for given sessionId", 342 + "Chat", 343 + vec![ 344 + ("/help", "Show this help message"), 345 + ("/bye", "Exit the REPL"), 346 + ], 334 347 ), 335 - ("help", "Show this help message"), 336 - ("bye", "Exit the REPL"), 337 348 ]; 338 349 339 - // finding the length of the longest command, for padding purposes 340 - let max_length = help_list 341 - .iter() 342 - .fold(0, |acc, x| if x.0.len() > acc { x.0.len() } else { acc }); 343 - 344 350 println!("Available Commands:"); 345 351 346 - for help in help_list { 347 - let final_str = format!( 348 - " /{}{}\t\t{}", 349 - help.0, 350 - " ".repeat(max_length - help.0.len()), 351 - help.1 352 - ); 352 + for (heading, commands) in help_groups { 353 + println!(); 354 + println!(" {heading}"); 353 355 354 - println!("{}", final_str); 356 + for (command, description) in commands { 357 + println!(" {command:<24}{description}"); 358 + } 355 359 } 356 360 357 361 println!(); 358 - 359 - println!("\nDocumentation: https://tiles.run/book"); 360 - 362 + println!("Documentation: https://tiles.run/book"); 361 363 println!("Report issues: https://github.com/tilesprivacy/tiles/issues"); 362 364 println!(); 363 365 } ··· 397 399 .ok_or_else(|| anyhow!("Error getting FROM from modelfile due to"))?; 398 400 399 401 let system_prompt = modelfile.system.clone().unwrap_or("".to_owned()); 400 - println!("Running {} in interactive mode", modelname); 402 + println!("Running {}", modelname); 401 403 let current_user = get_current_user(&db_conn.common)?; 402 404 403 405 let config = Config::builder().auto_add_history(true).build();