anveesa 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.lock CHANGED
@@ -54,7 +54,7 @@ dependencies = [
54
54
 
55
55
  [[package]]
56
56
  name = "anveesa"
57
- version = "0.3.4"
57
+ version = "0.3.5"
58
58
  dependencies = [
59
59
  "anyhow",
60
60
  "base64",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "anveesa"
3
- version = "0.3.4"
3
+ version = "0.3.5"
4
4
  edition = "2024"
5
5
  default-run = "anveesa"
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anveesa",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "A terminal CLI that wraps AI providers (OpenAI-compatible APIs and local CLIs) into a single unified command",
5
5
  "main": "bin/anveesa.js",
6
6
  "bin": {
package/src/lib.rs CHANGED
@@ -143,15 +143,22 @@ async fn run_interactive(options: AskOptions) -> Result<()> {
143
143
 
144
144
  loop {
145
145
  print_input_separator(is_tty, width);
146
- let line = match read_prompt_line(&label, width, &mut paste_count) {
147
- Ok(PromptRead::Line(line)) => line,
148
- Ok(PromptRead::Interrupted) => continue,
149
- Ok(PromptRead::Eof) => {
150
- println!();
151
- break;
152
- }
153
- Err(error) => return Err(error).context("failed to read interactive prompt"),
154
- };
146
+ let (line, ctrl_v_image) =
147
+ match read_prompt_line(&label, width, &mut paste_count, images_available) {
148
+ Ok(PromptRead::Line(line, img)) => (line, img),
149
+ Ok(PromptRead::Interrupted) => continue,
150
+ Ok(PromptRead::Eof) => {
151
+ println!();
152
+ break;
153
+ }
154
+ Err(error) => return Err(error).context("failed to read interactive prompt"),
155
+ };
156
+
157
+ // Ctrl+V image takes precedence over a previously pending image.
158
+ if let Some(img) = ctrl_v_image {
159
+ last_image_fp = Some(image_fingerprint(&img));
160
+ pending_image = Some(img);
161
+ }
155
162
 
156
163
  let prompt = line.trim().to_string();
157
164
  if prompt.is_empty() {
@@ -1313,7 +1320,7 @@ fn print_session_header(
1313
1320
  }
1314
1321
 
1315
1322
  enum PromptRead {
1316
- Line(String),
1323
+ Line(String, Option<ImageAttachment>),
1317
1324
  Interrupted,
1318
1325
  Eof,
1319
1326
  }
@@ -1466,13 +1473,28 @@ impl RawPromptMode {
1466
1473
  }
1467
1474
  }
1468
1475
 
1469
- fn read_prompt_line(label: &str, width: usize, paste_count: &mut usize) -> Result<PromptRead> {
1476
+ fn read_prompt_line(
1477
+ label: &str,
1478
+ width: usize,
1479
+ paste_count: &mut usize,
1480
+ images_available: bool,
1481
+ ) -> Result<PromptRead> {
1470
1482
  let _raw_mode = RawPromptMode::enter()?;
1471
1483
  let mut input = io::stdin().lock();
1472
1484
  let mut buffer = PromptBuffer::default();
1473
1485
  let mut display_rows = 1usize;
1486
+ let mut ctrl_v_image: Option<ImageAttachment> = None;
1487
+
1488
+ // Compose the visible prompt label, optionally prefixed with an image indicator.
1489
+ let effective_label = |img: &Option<ImageAttachment>| -> String {
1490
+ if img.is_some() {
1491
+ format!("\x1b[2m[📎]\x1b[0m {label}")
1492
+ } else {
1493
+ label.to_string()
1494
+ }
1495
+ };
1474
1496
 
1475
- print!("{label}");
1497
+ print!("{}", effective_label(&ctrl_v_image));
1476
1498
  io::stdout().flush().context("failed to write prompt")?;
1477
1499
 
1478
1500
  loop {
@@ -1484,9 +1506,11 @@ fn read_prompt_line(label: &str, width: usize, paste_count: &mut usize) -> Resul
1484
1506
  match byte[0] {
1485
1507
  b'\r' | b'\n' => {
1486
1508
  println!();
1487
- return Ok(PromptRead::Line(buffer.full));
1509
+ return Ok(PromptRead::Line(buffer.full, ctrl_v_image));
1488
1510
  }
1489
1511
  3 => {
1512
+ // Ctrl+C — discard any pasted image too
1513
+ ctrl_v_image = None;
1490
1514
  println!("^C");
1491
1515
  return Ok(PromptRead::Interrupted);
1492
1516
  }
@@ -1494,30 +1518,51 @@ fn read_prompt_line(label: &str, width: usize, paste_count: &mut usize) -> Resul
1494
1518
  8 | 127 => {
1495
1519
  // Backspace
1496
1520
  buffer.pop_last();
1497
- display_rows = redraw_prompt_line(label, &buffer.display, display_rows, width)?;
1521
+ let lbl = effective_label(&ctrl_v_image);
1522
+ display_rows = redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1498
1523
  }
1499
1524
  21 => {
1500
1525
  // Ctrl+U / Cmd+Delete — erase entire line
1501
1526
  buffer.clear_all();
1502
- display_rows = redraw_prompt_line(label, &buffer.display, display_rows, width)?;
1527
+ let lbl = effective_label(&ctrl_v_image);
1528
+ display_rows = redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1529
+ }
1530
+ 22 if images_available => {
1531
+ // Ctrl+V — paste image from clipboard
1532
+ match grab_clipboard_image() {
1533
+ Some(img) => {
1534
+ ctrl_v_image = Some(img);
1535
+ let lbl = effective_label(&ctrl_v_image);
1536
+ display_rows =
1537
+ redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1538
+ }
1539
+ None => {
1540
+ // No image in clipboard — ring the bell
1541
+ print!("\x07");
1542
+ let _ = io::stdout().flush();
1543
+ }
1544
+ }
1503
1545
  }
1504
1546
  23 => {
1505
1547
  // Ctrl+W / Option+Delete — erase last word
1506
1548
  buffer.pop_word();
1507
- display_rows = redraw_prompt_line(label, &buffer.display, display_rows, width)?;
1549
+ let lbl = effective_label(&ctrl_v_image);
1550
+ display_rows = redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1508
1551
  }
1509
1552
  0x1b => {
1510
1553
  let sequence = read_escape_sequence(&mut input)?;
1511
1554
  if sequence == b"[200~" {
1512
1555
  let paste = normalize_pasted_text(read_bracketed_paste(&mut input)?);
1513
1556
  push_paste(&mut buffer, paste, paste_count);
1514
- display_rows = redraw_prompt_line(label, &buffer.display, display_rows, width)?;
1557
+ let lbl = effective_label(&ctrl_v_image);
1558
+ display_rows = redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1515
1559
  }
1516
1560
  }
1517
1561
  byte if byte >= 0x20 && byte != 0x7f => {
1518
1562
  if let Some(ch) = read_utf8_char(byte, &mut input)? {
1519
1563
  buffer.push_text(ch.encode_utf8(&mut [0; 4]));
1520
- display_rows = redraw_prompt_line(label, &buffer.display, display_rows, width)?;
1564
+ let lbl = effective_label(&ctrl_v_image);
1565
+ display_rows = redraw_prompt_line(&lbl, &buffer.display, display_rows, width)?;
1521
1566
  }
1522
1567
  }
1523
1568
  _ => {}