pi-paster 0.1.4 → 0.1.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/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { existsSync, readFileSync, statSync, unlinkSync } from "node:fs";
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { isAbsolute, join, resolve } from "node:path";
6
- import { Image, getCellDimensions, getImageDimensions, truncateToWidth } from "@earendil-works/pi-tui";
6
+ import { Image, getCellDimensions, getImageDimensions, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
7
7
  import { CustomEditor } from "@earendil-works/pi-coding-agent";
8
8
  //#region src/types.ts
9
9
  const EXTENSION_NAME = "paster";
@@ -408,6 +408,11 @@ var PasterEditor = class extends CustomEditor {
408
408
  };
409
409
  //#endregion
410
410
  //#region src/preview.ts
411
+ function formatAttachmentLine(attachment, width, style) {
412
+ const maxWidth = Math.max(1, width);
413
+ const line = style(`Attached ${attachment.placeholder} ${attachment.originalPath}`);
414
+ return visibleWidth(line) > maxWidth ? truncateToWidth(line, maxWidth, "") : line;
415
+ }
411
416
  var ImagePreviewMessage = class {
412
417
  images;
413
418
  constructor(attachments, theme) {
@@ -423,7 +428,7 @@ var ImagePreviewMessage = class {
423
428
  const lines = [];
424
429
  for (let index = 0; index < this.attachments.length; index++) {
425
430
  const attachment = this.attachments[index];
426
- lines.push(this.theme.fallbackColor(`Attached ${attachment.placeholder} ${attachment.originalPath}`));
431
+ lines.push(formatAttachmentLine(attachment, width, this.theme.fallbackColor));
427
432
  lines.push(...this.images[index].render(width));
428
433
  }
429
434
  return lines;
@@ -448,8 +453,7 @@ var CursorImagePreviewWidget = class {
448
453
  this.image.invalidate();
449
454
  }
450
455
  headerLine(width) {
451
- const title = `Attached ${this.attachment.placeholder} ${this.attachment.originalPath}`;
452
- return this.theme.title(truncateToWidth(title, Math.max(1, width), ""));
456
+ return formatAttachmentLine(this.attachment, width, this.theme.title);
453
457
  }
454
458
  createImage(attachment, maxWidthCells = 60) {
455
459
  return new Image(attachment.data, attachment.mimeType, { fallbackColor: this.theme.accent }, {
@@ -607,12 +611,21 @@ function paster(pi, config = {}) {
607
611
  }
608
612
  store.clear();
609
613
  });
614
+ function previewMessage(attachments) {
615
+ return {
616
+ customType: "paster-preview",
617
+ content: "",
618
+ display: true,
619
+ details: { placeholders: attachments.map((attachment) => attachment.placeholder) }
620
+ };
621
+ }
610
622
  pi.on("input", (event, ctx) => {
611
623
  if (event.source === "extension") return { action: "continue" };
612
624
  if (ctx.hasUI) activeEditor?.clearCursorPreview();
613
625
  const attachments = store.matchingPlaceholders(event.text);
614
626
  if (attachments.length === 0) return { action: "continue" };
615
- pendingPreview = attachments;
627
+ if (ctx.isIdle()) pendingPreview = attachments;
628
+ else pi.sendMessage(previewMessage(attachments), { deliverAs: "followUp" });
616
629
  return {
617
630
  action: "transform",
618
631
  text: event.text,
@@ -621,14 +634,9 @@ function paster(pi, config = {}) {
621
634
  });
622
635
  pi.on("before_agent_start", () => {
623
636
  if (pendingPreview.length === 0) return;
624
- const placeholders = pendingPreview.map((attachment) => attachment.placeholder);
637
+ const message = previewMessage(pendingPreview);
625
638
  pendingPreview = [];
626
- return { message: {
627
- customType: "paster-preview",
628
- content: "",
629
- display: true,
630
- details: { placeholders }
631
- } };
639
+ return { message };
632
640
  });
633
641
  }
634
642
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-paster",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Pi extension that turns pasted image paths into first-class image attachments.",
5
5
  "keywords": [
6
6
  "image-attachments",
@@ -55,6 +55,6 @@
55
55
  "extensions": [
56
56
  "./src/index.ts"
57
57
  ],
58
- "image": "https://unpkg.com/pi-paster@0.1.4/docs/preview.png"
58
+ "image": "https://unpkg.com/pi-paster@0.1.5/docs/preview.png"
59
59
  }
60
60
  }
package/src/index.ts CHANGED
@@ -110,6 +110,15 @@ export default function paster(pi: ExtensionAPI, config: PasterConfig = {}): voi
110
110
  store.clear();
111
111
  });
112
112
 
113
+ function previewMessage(attachments: ImageAttachment[]) {
114
+ return {
115
+ customType: "paster-preview",
116
+ content: "",
117
+ display: true,
118
+ details: { placeholders: attachments.map((attachment) => attachment.placeholder) },
119
+ };
120
+ }
121
+
113
122
  pi.on("input", (event, ctx) => {
114
123
  if (event.source === "extension") return { action: "continue" as const };
115
124
  if (ctx.hasUI) {
@@ -118,7 +127,14 @@ export default function paster(pi: ExtensionAPI, config: PasterConfig = {}): voi
118
127
 
119
128
  const attachments = store.matchingPlaceholders(event.text);
120
129
  if (attachments.length === 0) return { action: "continue" as const };
121
- pendingPreview = attachments;
130
+
131
+ if (ctx.isIdle()) {
132
+ pendingPreview = attachments;
133
+ } else {
134
+ // Queued steer/follow-up messages do not fire before_agent_start when they are
135
+ // later delivered by the running agent, so enqueue the preview alongside them now.
136
+ pi.sendMessage(previewMessage(attachments), { deliverAs: "followUp" });
137
+ }
122
138
 
123
139
  return {
124
140
  action: "transform" as const,
@@ -129,15 +145,8 @@ export default function paster(pi: ExtensionAPI, config: PasterConfig = {}): voi
129
145
 
130
146
  pi.on("before_agent_start", () => {
131
147
  if (pendingPreview.length === 0) return;
132
- const placeholders = pendingPreview.map((attachment) => attachment.placeholder);
148
+ const message = previewMessage(pendingPreview);
133
149
  pendingPreview = [];
134
- return {
135
- message: {
136
- customType: "paster-preview",
137
- content: "",
138
- display: true,
139
- details: { placeholders },
140
- },
141
- };
150
+ return { message };
142
151
  });
143
152
  }
package/src/preview.ts CHANGED
@@ -4,9 +4,20 @@ import {
4
4
  type Component,
5
5
  type ImageTheme,
6
6
  truncateToWidth,
7
+ visibleWidth,
7
8
  } from "@earendil-works/pi-tui";
8
9
  import type { ImageAttachment } from "./types.ts";
9
10
 
11
+ function formatAttachmentLine(
12
+ attachment: ImageAttachment,
13
+ width: number,
14
+ style: (text: string) => string,
15
+ ): string {
16
+ const maxWidth = Math.max(1, width);
17
+ const line = style(`Attached ${attachment.placeholder} ${attachment.originalPath}`);
18
+ return visibleWidth(line) > maxWidth ? truncateToWidth(line, maxWidth, "") : line;
19
+ }
20
+
10
21
  export class ImagePreviewMessage implements Component {
11
22
  private readonly images: Image[];
12
23
 
@@ -28,9 +39,7 @@ export class ImagePreviewMessage implements Component {
28
39
  const lines: string[] = [];
29
40
  for (let index = 0; index < this.attachments.length; index++) {
30
41
  const attachment = this.attachments[index]!;
31
- lines.push(
32
- this.theme.fallbackColor(`Attached ${attachment.placeholder} ${attachment.originalPath}`),
33
- );
42
+ lines.push(formatAttachmentLine(attachment, width, this.theme.fallbackColor));
34
43
  lines.push(...this.images[index]!.render(width));
35
44
  }
36
45
  return lines;
@@ -68,8 +77,7 @@ export class CursorImagePreviewWidget implements Component {
68
77
  }
69
78
 
70
79
  private headerLine(width: number): string {
71
- const title = `Attached ${this.attachment.placeholder} ${this.attachment.originalPath}`;
72
- return this.theme.title(truncateToWidth(title, Math.max(1, width), ""));
80
+ return formatAttachmentLine(this.attachment, width, this.theme.title);
73
81
  }
74
82
 
75
83
  private createImage(attachment: ImageAttachment, maxWidthCells = 60): Image {