@heylemon/lemonade 2026.2.10 → 2026.2.20

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.2.10",
3
- "commit": "2bb3a320f5a368e41a0f53343a808b78554588ea",
4
- "builtAt": "2026-02-19T08:24:56.786Z"
2
+ "version": "2026.2.20",
3
+ "commit": "e49805d8488591840c53c697252f44ddfa686424",
4
+ "builtAt": "2026-02-19T10:41:08.140Z"
5
5
  }
@@ -1 +1 @@
1
- f7966aa09695def5e0e96affa3cd2b408a5cf50eae8ab0cc1cee83bb03f30b01
1
+ 097295da59112a03be00996fb0b774b800b315f4a291f6253930f4d42aa9a1ac
@@ -378,16 +378,21 @@ async function probeLocalParakeet() {
378
378
  }
379
379
  })();
380
380
  parakeetProbeCache.set("parakeet", resolved);
381
- return resolved;
381
+ const isHealthy = await resolved;
382
+ // Do not permanently cache negative probes. The local transcription server
383
+ // may come up shortly after startup; retry on subsequent audio messages.
384
+ if (!isHealthy) {
385
+ parakeetProbeCache.delete("parakeet");
386
+ }
387
+ return isHealthy;
382
388
  }
383
389
  async function resolveLocalParakeetEntry() {
384
390
  if (!(await probeLocalParakeet()))
385
391
  return null;
386
- if (!(await hasBinary("curl")))
387
- return null;
388
392
  return {
389
393
  type: "cli",
390
- command: "curl",
394
+ // Use absolute path so we don't depend on PATH contents.
395
+ command: "/usr/bin/curl",
391
396
  args: [
392
397
  "-sf",
393
398
  "-X",
@@ -333,7 +333,9 @@ export async function monitorWebInbox(options) {
333
333
  const maxBytes = maxMb * 1024 * 1024;
334
334
  const saved = await saveMediaBuffer(inboundMedia.buffer, inboundMedia.mimetype, "inbound", maxBytes);
335
335
  mediaPath = saved.path;
336
- mediaType = inboundMedia.mimetype;
336
+ // Prefer detected mime from saved media (sniffed from bytes), then
337
+ // fall back to transport-provided mimetype.
338
+ mediaType = saved.contentType ?? inboundMedia.mimetype;
337
339
  // Extract text from document attachments (PDF, DOCX, etc.)
338
340
  if (mediaPath && isExtractableDocument(mediaType)) {
339
341
  try {
@@ -59,6 +59,12 @@ If a tool call fails or returns an error:
59
59
  - Keep operational progress in tool/status updates, not in the final answer text.
60
60
  - For file search tasks, never narrate each attempt ("looking for...", "trying broader search..."). Return one concise final result only.
61
61
 
62
+ ## PDF Form Integrity
63
+ - When editing an existing PDF form, preserve the original layout and formatting.
64
+ - Never regenerate or redesign pages for "fill this PDF" tasks.
65
+ - Prefer true fillable field updates; use overlay/annotation fallback only when no fields exist.
66
+ - Do not overwrite the original PDF. Always save a separate filled output file.
67
+
62
68
  ## Writing Style - KEEP IT CLEAN
63
69
 
64
70
  **Avoid excessive emojis:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "2026.2.10",
3
+ "version": "2026.2.20",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
@@ -18,3 +18,5 @@ nano-pdf edit deck.pdf 1 "Change the title to 'Q3 Results' and fix the typo in t
18
18
  Notes:
19
19
  - Page numbers are 0-based or 1-based depending on the tool’s version/config; if the result looks off by one, retry with the other.
20
20
  - Always sanity-check the output PDF before sending it out.
21
+ - Do not use this tool for official form-filling where exact template fidelity is required.
22
+ - For "fill this existing form" tasks, use the `pdf` skill `FORMS.md` workflow instead.
@@ -1,5 +1,17 @@
1
1
  **CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
2
2
 
3
+ ## Safety Rules (Exact-Formatting Forms)
4
+
5
+ For official/sensitive forms, follow these rules strictly:
6
+
7
+ - Keep the original PDF untouched. Always write to a new file (for example, `original.filled.pdf`).
8
+ - Do not overwrite the input file.
9
+ - Do not regenerate pages or rebuild the PDF from extracted text.
10
+ - Do not use "create PDF" workflows for form-filling requests.
11
+ - Prefer true form-field filling whenever available; this preserves layout best.
12
+ - If the file has no fillable fields, explain that non-fillable fallback uses overlays/annotations and may not be pixel-perfect in every viewer.
13
+ - For non-fillable fallback, ask for a brief confirmation before writing output when exact legal formatting is required.
14
+
3
15
  If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
4
16
  `python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
5
17
 
@@ -74,6 +86,7 @@ Then analyze the images to determine the purpose of each form field (make sure t
74
86
  - Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
75
87
  `python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
76
88
  This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
89
+ - Use a new output filename and keep the input unchanged.
77
90
 
78
91
  # Non-fillable fields
79
92
  If the PDF doesn't have fillable form fields, you'll add text annotations. First try to extract coordinates from the PDF structure (more accurate), then fall back to visual estimation if needed.
@@ -282,6 +295,7 @@ Fix any reported errors in fields.json before proceeding.
282
295
 
283
296
  The fill script auto-detects the coordinate system and handles conversion:
284
297
  `python scripts/fill_pdf_form_with_annotations.py <input.pdf> fields.json <output.pdf>`
298
+ - Use a new output filename and keep the input unchanged.
285
299
 
286
300
  ## Step 4: Verify Output
287
301
 
@@ -6,6 +6,19 @@ license: Proprietary. LICENSE.txt has complete terms
6
6
 
7
7
  # PDF Processing Guide
8
8
 
9
+ ## Document Integrity Mode (CRITICAL)
10
+
11
+ When the user asks to **fill an existing PDF** (especially official/government forms), preserve layout exactly:
12
+
13
+ - Never recreate the document from scratch.
14
+ - Never reflow, rewrite, or "clean up" page content.
15
+ - Never convert PDF -> DOCX/Markdown -> PDF for form filling tasks.
16
+ - Never replace or redesign page templates.
17
+ - Always keep the original file unchanged and write to a new output file.
18
+ - Use the workflow in `FORMS.md` exactly (fillable fields first, then fallback path).
19
+
20
+ If the user asks for exact formatting, treat that as strict mode and prioritize minimal-delta edits only.
21
+
9
22
  ## Overview
10
23
 
11
24
  This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see REFERENCE.md. If you need to fill out a PDF form, read FORMS.md and follow its instructions.
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import sys
3
+ import os
3
4
 
4
5
  from pypdf import PdfReader, PdfWriter
5
6
 
@@ -10,6 +11,12 @@ from extract_form_field_info import get_field_info
10
11
 
11
12
 
12
13
  def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str):
14
+ input_abs = os.path.abspath(input_pdf_path)
15
+ output_abs = os.path.abspath(output_pdf_path)
16
+ if input_abs == output_abs:
17
+ print("ERROR: Refusing to overwrite input PDF. Use a different output path.")
18
+ sys.exit(1)
19
+
13
20
  with open(fields_json_path) as f:
14
21
  fields = json.load(f)
15
22
  # Group by page number.
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import sys
3
+ import os
3
4
 
4
5
  from pypdf import PdfReader, PdfWriter
5
6
  from pypdf.annotations import FreeText
@@ -45,6 +46,11 @@ def transform_from_pdf_coords(bbox, pdf_height):
45
46
 
46
47
  def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):
47
48
  """Fill the PDF form with data from fields.json"""
49
+ input_abs = os.path.abspath(input_pdf_path)
50
+ output_abs = os.path.abspath(output_pdf_path)
51
+ if input_abs == output_abs:
52
+ print("ERROR: Refusing to overwrite input PDF. Use a different output path.")
53
+ sys.exit(1)
48
54
 
49
55
  # `fields.json` format described in FORMS.md.
50
56
  with open(fields_json_path, "r") as f: