pi-copy-message 2.0.0 → 2.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 - 2026-08-17
4
+
5
+ - Add an `Alt+C` picker filter and `MessageVisibility.showCustom` option for showing or hiding custom messages.
6
+ - Hide picker shortcut hints that collide with configured selection bindings.
7
+
3
8
  ## 2.0.0 - 2026-08-06
4
9
 
5
10
  - Set pi 0.84.0 as the minimum supported version and refresh the local coding-agent and TUI development baseline.
package/README.md CHANGED
@@ -11,7 +11,7 @@ A [pi](https://github.com/earendil-works/pi-mono) extension that adds `/copy-mes
11
11
  - Copies raw stored session message text, not rendered terminal lines
12
12
  - Shows messages in chat order: oldest at top, newest at bottom
13
13
  - Selects the newest visible message by default
14
- - Supports role filters for user, assistant, and tool/bash messages
14
+ - Supports role filters for user, assistant, tool/bash, and custom messages
15
15
  - Hides tool/bash messages by default
16
16
  - Supports type-to-filter search across role and message text, with `time:<term>` for timestamp search
17
17
  - Supports Home/End jumps for oldest/newest visible messages
@@ -103,6 +103,7 @@ Copy with role and timestamp metadata instead of raw text only:
103
103
  | `Ctrl+U` | Toggle user messages |
104
104
  | `Ctrl+A` | Toggle assistant messages |
105
105
  | `Ctrl+T` | Toggle tool/bash messages |
106
+ | `Alt+C` | Toggle custom messages |
106
107
  | `Tab` | Toggle a wrapped preview of the selected message |
107
108
  | `Alt+M` | Toggle raw vs metadata copy format |
108
109
  | Configured `tui.select.confirm` (default: `Enter`) | Copy selected message text |
@@ -116,9 +117,9 @@ Copy with role and timestamp metadata instead of raw text only:
116
117
  - General search does not match timestamps; use `time:<term>` when you want to search by displayed time.
117
118
  - Filter labels honor the active pi theme.
118
119
  - Selection key hints show the active Pi bindings. Configured selection actions take precedence if they collide with the picker's filter, preview, or format shortcuts.
119
- - Hidden custom messages are excluded in both current `custom_message` entries and legacy message-wrapped entries; only messages with `display: true` can appear or be copied.
120
+ - Only custom messages with `display: true` can appear or be copied from current `custom_message` entries or legacy message-wrapped entries; visible ones can be toggled with `Alt+C`.
120
121
  - Copy notifications include the role and a short preview so you can verify what was copied.
121
- - `/copy-message latest` respects default visibility: user and assistant messages are visible, tool/bash messages are hidden. If only hidden messages exist, it falls back to the newest message so the command still does something useful.
122
+ - `/copy-message latest` and numbered selectors include user, assistant, and visible custom messages by default while hiding tool/bash messages. `Alt+C` only affects the picker. If only hidden messages exist, `latest` falls back to the newest message so the command still does something useful.
122
123
  - `/copy-message` with no direct selector requires interactive TUI mode because the picker is a custom TUI component.
123
124
  - Direct commands such as `/copy-user`, `/copy-message latest`, and `/copy-message 3` do not require TUI mode, though non-UI modes may not display notifications.
124
125
 
@@ -241,12 +241,14 @@ export interface MessageVisibility {
241
241
  showAssistant: boolean;
242
242
  showUser: boolean;
243
243
  showTools: boolean;
244
+ showCustom: boolean;
244
245
  }
245
246
 
246
247
  function isVisibleMessage(message: CopyableMessage, visibility: MessageVisibility): boolean {
247
248
  if (isToolMessage(message)) return visibility.showTools;
248
249
  if (message.role === "assistant") return visibility.showAssistant;
249
250
  if (message.role === "user") return visibility.showUser;
251
+ if (message.role === "custom") return visibility.showCustom;
250
252
  return true;
251
253
  }
252
254
 
@@ -274,7 +276,7 @@ export function filteredMessages(messages: CopyableMessage[], visibility: Messag
274
276
  }
275
277
 
276
278
  export function defaultVisibleMessages(messages: CopyableMessage[]): CopyableMessage[] {
277
- return filteredMessages(messages, { showAssistant: true, showUser: true, showTools: false });
279
+ return filteredMessages(messages, { showAssistant: true, showUser: true, showTools: false, showCustom: true });
278
280
  }
279
281
 
280
282
  export function latestDefaultMessage(messages: CopyableMessage[]): CopyableMessage | undefined {
@@ -370,7 +372,7 @@ function bindingHint(keybindings: PickerKeybindings | undefined, action: "up" |
370
372
  return keys.reduce((shortest, key) => visibleWidth(key) < visibleWidth(shortest) ? key : shortest, keys[0] ?? "unbound");
371
373
  }
372
374
 
373
- function helpLines(width: number, keybindings?: PickerKeybindings, tuiMode: TuiMode = "regular"): string[] {
375
+ function helpLines(width: number, keybindings: PickerKeybindings | undefined, tuiMode: TuiMode, hasCustomMessages: boolean): string[] {
374
376
  const up = bindingHint(keybindings, "up");
375
377
  const down = bindingHint(keybindings, "down");
376
378
  const confirm = bindingHint(keybindings, "confirm");
@@ -390,7 +392,8 @@ function helpLines(width: number, keybindings?: PickerKeybindings, tuiMode: TuiM
390
392
  { hint: "T", data: "\x14" },
391
393
  ].filter(({ data }) => available(data));
392
394
  const filterHint = filters.length > 0 ? `Ctrl+${filters.map(({ hint }) => hint).join("/")} filters` : undefined;
393
- const meta = available("\x1bm") ? "Alt+M meta" : undefined;
395
+ const custom = hasCustomMessages && available("\x1b[99;3u") ? "Alt+C custom" : undefined;
396
+ const meta = available("\x1b[109;3u") ? "Alt+M meta" : undefined;
394
397
  const jumps = (tuiMode === "fullscreen"
395
398
  ? [
396
399
  { hint: "Ctrl+Home", data: "\x1b[1;5H" },
@@ -415,7 +418,7 @@ function helpLines(width: number, keybindings?: PickerKeybindings, tuiMode: TuiM
415
418
  return lines;
416
419
  }
417
420
 
418
- for (const hint of width < 74 ? [jumpHint, peek, filterHint, meta] : ["type search", jumpHint, peek, filterHint, meta]) {
421
+ for (const hint of width < 74 ? [jumpHint, peek, filterHint, custom, meta] : ["type search", jumpHint, peek, filterHint, custom, meta]) {
419
422
  if (hint && visibleWidth(join(...optional, hint, ...core)) <= width) optional.push(hint);
420
423
  }
421
424
 
@@ -429,6 +432,7 @@ export class CopyMessagePickerState {
429
432
  showAssistant: true,
430
433
  showUser: true,
431
434
  showTools: false,
435
+ showCustom: true,
432
436
  };
433
437
  search = "";
434
438
  visibleMessages: CopyableMessage[];
@@ -456,6 +460,7 @@ export class CopyMessagePickerState {
456
460
  const maxVisible = Math.min(this.visibleMessages.length, MAX_VISIBLE_MESSAGES);
457
461
  const start = maxVisible === 0 ? 0 : Math.max(0, Math.min(this.selectedIndex - maxVisible + 1, this.visibleMessages.length - maxVisible));
458
462
  const end = Math.min(this.visibleMessages.length, start + maxVisible);
463
+ const hasCustomMessages = this.messages.some((message) => message.role === "custom");
459
464
  const userState = filterLabel(theme, "user", this.visibility.showUser, "warning");
460
465
  const assistantState = filterLabel(theme, "assistant", this.visibility.showAssistant, "accent");
461
466
  const toolState = filterLabel(theme, "tools", this.visibility.showTools, "dim");
@@ -481,9 +486,11 @@ export class CopyMessagePickerState {
481
486
  }
482
487
 
483
488
  const position = this.visibleMessages.length === 0 ? "0/0" : `${this.selectedIndex + 1}/${this.visibleMessages.length}`;
484
- lines.push(`${theme.fg("dim", `(${position})`)} · ${userState} · ${assistantState} · ${toolState} · ${formatState} · ${searchState}`);
489
+ const filters = [userState, assistantState, toolState];
490
+ if (hasCustomMessages) filters.push(filterLabel(theme, "custom", this.visibility.showCustom, "dim"));
491
+ lines.push([theme.fg("dim", `(${position})`), ...filters, searchState, formatState].join(" · "));
485
492
  lines.push("");
486
- lines.push(...helpLines(width, keybindings, tuiMode).map((line) => hotkeyHint(theme, line)));
493
+ lines.push(...helpLines(width, keybindings, tuiMode, hasCustomMessages).map((line) => hotkeyHint(theme, line)));
487
494
  lines.push("");
488
495
  return lines.map((line) => truncateToWidth(line, width, ""));
489
496
  }
@@ -524,6 +531,11 @@ export class CopyMessagePickerState {
524
531
  this.refreshMessages();
525
532
  return "render";
526
533
  }
534
+ if (matchesKey(data, "alt+c")) {
535
+ this.visibility.showCustom = !this.visibility.showCustom;
536
+ this.refreshMessages();
537
+ return "render";
538
+ }
527
539
  if (matchesKey(data, "alt+m")) {
528
540
  this.format = this.format === "raw" ? "metadata" : "raw";
529
541
  return "render";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-copy-message",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Pi extension with a searchable TUI picker for copying raw session messages",
5
5
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
6
6
  "license": "MIT",