@maheidem/pi-auto-image-attach 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/index.ts +47 -22
  2. package/package.json +5 -3
package/index.ts CHANGED
@@ -29,27 +29,31 @@ const IMAGE_EXTENSIONS = new Set([
29
29
  ".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp",
30
30
  ]);
31
31
 
32
+ /** Images above this size are skipped (protects context/cost). */
33
+ const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
34
+
32
35
  function isImagePath(filePath: string): boolean {
33
36
  return IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
34
37
  }
35
38
 
36
39
  async function detectImageMimeType(filePath: string): Promise<string | null> {
37
- try {
38
- const fd = await fs.promises.open(filePath, "r");
39
- const buffer = Buffer.alloc(4100);
40
- const { bytesRead } = await fd.read(buffer, 0, 4100, 0);
41
- await fd.close();
42
- const s = buffer.subarray(0, bytesRead);
43
- if (s[0] === 0x89 && s[1] === 0x50 && s[2] === 0x4e && s[3] === 0x47 &&
44
- s[4] === 0x0d && s[5] === 0x0a && s[6] === 0x1a && s[7] === 0x0a) return "image/png";
45
- if (s[0] === 0xff && s[1] === 0xd8 && s[2] === 0xff) return "image/jpeg";
46
- if (s[0] === 0x47 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x38) return "image/gif";
47
- if (s[0] === 0x52 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x46 &&
48
- s[8] === 0x57 && s[9] === 0x45 && s[10] === 0x42 && s[11] === 0x50) return "image/webp";
49
- if (s[0] === 0x42 && s[1] === 0x4d) return "image/bmp";
50
- return null;
51
- }
52
- catch { return null; }
40
+ const fd = await fs.promises.open(filePath, "r");
41
+ const buffer = Buffer.alloc(4100);
42
+ const { bytesRead } = await fd.read(buffer, 0, 4100, 0);
43
+ await fd.close();
44
+ const s = buffer.subarray(0, bytesRead);
45
+ if (s[0] === 0x89 && s[1] === 0x50 && s[2] === 0x4e && s[3] === 0x47 &&
46
+ s[4] === 0x0d && s[5] === 0x0a && s[6] === 0x1a && s[7] === 0x0a) return "image/png";
47
+ if (s[0] === 0xff && s[1] === 0xd8 && s[2] === 0xff) return "image/jpeg";
48
+ if (s[0] === 0x47 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x38) return "image/gif";
49
+ if (s[0] === 0x52 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x46 &&
50
+ s[8] === 0x57 && s[9] === 0x45 && s[10] === 0x42 && s[11] === 0x50) return "image/webp";
51
+ if (s[0] === 0x42 && s[1] === 0x4d) return "image/bmp";
52
+ return null;
53
+ }
54
+
55
+ function skip(filePath: string, reason: string): void {
56
+ console.error(`[auto-image-attach] skipped ${filePath}: ${reason}`);
53
57
  }
54
58
 
55
59
  interface ImagePathEntry {
@@ -69,10 +73,12 @@ export default function (pi: ExtensionAPI) {
69
73
  const extRegex = /\.(png|jpe?g|webp|gif|bmp)($|["'\s,;:])/gi;
70
74
  let m;
71
75
  while ((m = extRegex.exec(text)) !== null) {
72
- const end = m.index + m[0].length;
76
+ // m[0] may include a trailing delimiter (space/quote/punct); exclude it from the path
77
+ const end = m.index + m[0].length - (m[2] ? m[2].length : 0);
73
78
  // Scan back to unescaped space or beginning
74
79
  let start = end - 1;
75
80
  while (start > 0 && !(text[start] === " " && text[start - 1] !== "\\")) start--;
81
+ if (text[start] === " " && text[start - 1] !== "\\") start++; // drop the delimiter space
76
82
  const raw = text.substring(start, end);
77
83
  const clean = raw.replace(/\\/g, "");
78
84
  if ((clean.startsWith("/") || clean.startsWith("./") || clean.startsWith("../"))
@@ -85,20 +91,39 @@ export default function (pi: ExtensionAPI) {
85
91
 
86
92
  // Read and encode images
87
93
  const images: ImageContent[] = [];
88
- for (const { cleanPath } of entries) {
94
+ const attached: string[] = []; // rawPaths of successfully attached entries
95
+ for (const { rawPath, cleanPath } of entries) {
89
96
  try {
97
+ let size = 0;
98
+ try {
99
+ size = fs.statSync(cleanPath).size;
100
+ } catch (err) {
101
+ skip(cleanPath, `stat failed (${(err as Error).message})`);
102
+ continue;
103
+ }
104
+ if (size > MAX_IMAGE_BYTES) {
105
+ // Oversize: do not attach; leave the path in the text (user may need it)
106
+ console.error(`[auto-image-attach] skipped ${cleanPath}: ${size} exceeds 10MB cap`);
107
+ continue;
108
+ }
90
109
  const mimeType = await detectImageMimeType(cleanPath);
91
- if (!mimeType) continue;
110
+ if (!mimeType) {
111
+ skip(cleanPath, "unrecognized image format");
112
+ continue;
113
+ }
92
114
  const data = fs.readFileSync(cleanPath).toString("base64");
93
115
  images.push({ type: "image", data, mimeType });
94
- } catch { /* skip unreadable */ }
116
+ attached.push(rawPath);
117
+ } catch (err) {
118
+ skip(cleanPath, `read failed (${(err as Error).message})`);
119
+ }
95
120
  }
96
121
 
97
122
  if (images.length === 0) return { action: "continue" };
98
123
 
99
- // Remove image paths from text
124
+ // Remove attached image paths from text (skipped entries keep their path)
100
125
  let cleaned = text;
101
- for (const { rawPath } of entries) {
126
+ for (const rawPath of attached) {
102
127
  const escaped = rawPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
128
  cleaned = cleaned.replace(new RegExp(escaped, "g"), "");
104
129
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maheidem/pi-auto-image-attach",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Pi extension: auto-attaches pasted/dragged image file paths as ImageContent blocks so the model actually sees the image.",
6
6
  "keywords": [
@@ -14,7 +14,9 @@
14
14
  "LICENSE"
15
15
  ],
16
16
  "scripts": {
17
- "typecheck": "tsc -p tsconfig.json"
17
+ "typecheck": "tsc -p tsconfig.json",
18
+ "test": "node --test *.test.ts",
19
+ "prepack": "npm run typecheck && npm test"
18
20
  },
19
21
  "peerDependencies": {
20
22
  "@earendil-works/pi-coding-agent": ">=0.84.0"
@@ -43,4 +45,4 @@
43
45
  "bugs": {
44
46
  "url": "https://github.com/Maheidem/pi-coder-management/issues"
45
47
  }
46
- }
48
+ }