Skip to content

Commit 08efa2d

Browse files
committed
Adding argument support for locally saved prompts
1 parent e3cf013 commit 08efa2d

File tree

3 files changed

+553
-3
lines changed

3 files changed

+553
-3
lines changed

crates/chat-cli/src/cli/chat/cli/prompts.rs

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ use thiserror::Error;
3232
use unicode_width::UnicodeWidthStr;
3333

3434
use crate::cli::chat::cli::editor::open_editor_file;
35+
use crate::cli::chat::prompt_args::{
36+
count_arguments,
37+
has_args_placeholder,
38+
substitute_arguments,
39+
validate_placeholders,
40+
ArgumentError,
41+
};
3542
use crate::cli::chat::tool_manager::PromptBundle;
3643
use crate::cli::chat::{
3744
ChatError,
@@ -110,6 +117,13 @@ impl Prompt {
110117

111118
/// Save content to the prompt file
112119
fn save_content(&self, content: &str) -> Result<(), GetPromptError> {
120+
// Validate argument placeholders before saving
121+
if let Err(arg_error) = validate_placeholders(content) {
122+
return Err(GetPromptError::General(
123+
eyre::eyre!("Invalid argument placeholders: {}", arg_error)
124+
));
125+
}
126+
113127
// Ensure parent directory exists
114128
if let Some(parent) = self.path.parent() {
115129
fs::create_dir_all(parent).map_err(GetPromptError::Io)?;
@@ -741,7 +755,22 @@ impl PromptsArgs {
741755
style::Print("\n"),
742756
)?;
743757
for name in &global_prompts {
758+
let prompts = Prompts::new(name, os).map_err(|e| ChatError::Custom(e.to_string().into()))?;
759+
let arg_count = if let Some((content, _)) = prompts.load_existing().map_err(|e| ChatError::Custom(e.to_string().into()))? {
760+
count_arguments(&content)
761+
} else {
762+
0
763+
};
764+
744765
queue!(session.stderr, style::Print("- "), style::Print(name))?;
766+
if arg_count > 0 {
767+
queue!(
768+
session.stderr,
769+
StyledText::secondary_fg(),
770+
style::Print(&format!(" ({} arguments)", arg_count)),
771+
StyledText::reset(),
772+
)?;
773+
}
745774
queue!(session.stderr, style::Print("\n"))?;
746775
}
747776
}
@@ -759,7 +788,22 @@ impl PromptsArgs {
759788
)?;
760789
for name in &local_prompts {
761790
let has_global_version = overridden_globals.contains(name);
791+
let prompts = Prompts::new(name, os).map_err(|e| ChatError::Custom(e.to_string().into()))?;
792+
let arg_count = if let Some((content, _)) = prompts.load_existing().map_err(|e| ChatError::Custom(e.to_string().into()))? {
793+
count_arguments(&content)
794+
} else {
795+
0
796+
};
797+
762798
queue!(session.stderr, style::Print("- "), style::Print(name),)?;
799+
if arg_count > 0 {
800+
queue!(
801+
session.stderr,
802+
StyledText::secondary_fg(),
803+
style::Print(&format!(" ({} arguments)", arg_count)),
804+
StyledText::reset(),
805+
)?;
806+
}
763807
if has_global_version {
764808
queue!(
765809
session.stderr,
@@ -1245,6 +1289,8 @@ impl PromptsSubcommand {
12451289
)?;
12461290

12471291
// Display usage example
1292+
let arg_count = count_arguments(content);
1293+
let has_args = has_args_placeholder(content);
12481294
queue!(
12491295
session.stderr,
12501296
style::SetAttribute(Attribute::Bold),
@@ -1253,10 +1299,73 @@ impl PromptsSubcommand {
12531299
StyledText::success_fg(),
12541300
style::Print("@"),
12551301
style::Print(name),
1302+
)?;
1303+
1304+
// Show argument placeholders if any exist
1305+
if arg_count > 0 {
1306+
if has_args {
1307+
queue!(session.stderr, style::Print(" <arguments>"))?;
1308+
}
1309+
if let Ok(positions) = validate_placeholders(content) {
1310+
for pos in positions {
1311+
queue!(
1312+
session.stderr,
1313+
style::Print(" <arg"),
1314+
style::Print(pos.to_string()),
1315+
style::Print(">"),
1316+
)?;
1317+
}
1318+
}
1319+
}
1320+
1321+
queue!(
1322+
session.stderr,
12561323
StyledText::reset(),
12571324
style::Print("\n\n"),
12581325
)?;
12591326

1327+
// Display argument information
1328+
if arg_count > 0 {
1329+
queue!(
1330+
session.stderr,
1331+
style::SetAttribute(Attribute::Bold),
1332+
style::Print("Arguments:"),
1333+
StyledText::reset_attributes(),
1334+
style::Print("\n"),
1335+
)?;
1336+
1337+
if has_args {
1338+
queue!(
1339+
session.stderr,
1340+
style::Print(" "),
1341+
StyledText::error_fg(),
1342+
style::Print("(required) "),
1343+
StyledText::brand_fg(),
1344+
style::Print("$ARGS or ${@}"),
1345+
StyledText::reset(),
1346+
style::Print(" - All provided arguments\n"),
1347+
)?;
1348+
}
1349+
1350+
if let Ok(positions) = validate_placeholders(content) {
1351+
for pos in positions {
1352+
queue!(
1353+
session.stderr,
1354+
style::Print(" "),
1355+
StyledText::error_fg(),
1356+
style::Print("(required) "),
1357+
StyledText::brand_fg(),
1358+
style::Print(&format!("arg{}", pos)),
1359+
StyledText::reset(),
1360+
style::Print(" - Positional argument "),
1361+
style::Print(pos.to_string()),
1362+
style::Print("\n"),
1363+
)?;
1364+
}
1365+
}
1366+
queue!(session.stderr, style::Print("\n"))?;
1367+
}
1368+
12601369
// Display content preview (first few lines)
12611370
queue!(
12621371
session.stderr,
@@ -1330,16 +1439,52 @@ impl PromptsSubcommand {
13301439
execute!(session.stderr)?;
13311440
}
13321441

1442+
// Handle argument substitution for file-based prompt
1443+
let final_content = if let Some(ref args) = arguments {
1444+
match substitute_arguments(&content, args) {
1445+
Ok((substituted, has_excess)) => {
1446+
if has_excess {
1447+
queue!(
1448+
session.stderr,
1449+
style::Print("\n"),
1450+
StyledText::warning_fg(),
1451+
style::Print("⚠ Warning: More arguments provided than expected. Ignoring extra arguments.\n"),
1452+
StyledText::reset(),
1453+
)?;
1454+
execute!(session.stderr)?;
1455+
}
1456+
substituted
1457+
},
1458+
Err(arg_error) => {
1459+
queue!(
1460+
session.stderr,
1461+
style::Print("\n"),
1462+
StyledText::error_fg(),
1463+
style::Print("Error processing arguments: "),
1464+
style::Print(arg_error.to_string()),
1465+
StyledText::reset(),
1466+
style::Print("\n"),
1467+
)?;
1468+
execute!(session.stderr)?;
1469+
return Ok(ChatState::PromptUser {
1470+
skip_printing_tools: true,
1471+
});
1472+
}
1473+
}
1474+
} else {
1475+
content.clone()
1476+
};
1477+
13331478
// Display the file-based prompt content to the user
1334-
display_file_prompt_content(&name, &content, session)?;
1479+
display_file_prompt_content(&name, &final_content, session)?;
13351480

13361481
// Handle local prompt
13371482
session.pending_prompts.clear();
13381483

1339-
// Create a PromptMessage from the local prompt content
1484+
// Create a PromptMessage from the processed prompt content
13401485
let prompt_message = PromptMessage {
13411486
role: PromptMessageRole::User,
1342-
content: PromptMessageContent::Text { text: content.clone() },
1487+
content: PromptMessageContent::Text { text: final_content },
13431488
};
13441489
session.pending_prompts.push_back(prompt_message);
13451490

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod checkpoint;
2020
mod line_tracker;
2121
mod parser;
2222
mod prompt;
23+
mod prompt_args;
2324
mod prompt_parser;
2425
pub mod server_messenger;
2526
use crate::cli::chat::checkpoint::CHECKPOINT_MESSAGE_MAX_LENGTH;

0 commit comments

Comments
 (0)