pi-copy-message 1.0.1 → 1.0.2

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2 - 2026-06-07
4
+
5
+ - Add `/copy-user` shortcut for copying the most recent user message directly.
6
+
3
7
  ## 1.0.1 - 2026-06-07
4
8
 
5
9
  - Improve npm package description and keywords.
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # pi copy-message extension
2
2
 
3
- A [pi](https://github.com/earendil-works/pi-mono) extension that adds `/copy-message`: a keyboard-first picker for copying raw session message text without terminal wrapping, padding, or rendered TUI artifacts.
3
+ A [pi](https://github.com/earendil-works/pi-mono) extension that adds `/copy-message`: a keyboard-first picker for copying raw session message text without terminal wrapping, padding, or rendered TUI artifacts. It also adds `/copy-user` as a direct shortcut for the most recent user message.
4
4
 
5
5
  `pi-copy-message` supersedes [`pi-copy-user-message`](https://github.com/fitchmultz/pi-copy-user-message), which only copied the most recent user message.
6
6
 
7
7
  ## What it does
8
8
 
9
9
  - Adds `/copy-message`
10
+ - Adds `/copy-user` for copying the most recent user message directly
10
11
  - Copies raw stored session message text, not rendered terminal lines
11
12
  - Shows messages in chat order: oldest at top, newest at bottom
12
13
  - Selects the newest visible message by default
@@ -50,6 +51,12 @@ Open the picker:
50
51
  /copy-message
51
52
  ```
52
53
 
54
+ Copy the most recent user message directly:
55
+
56
+ ```text
57
+ /copy-user
58
+ ```
59
+
53
60
  Copy the latest visible default message directly:
54
61
 
55
62
  ```text
@@ -86,29 +86,65 @@ function formatTime(timestamp: unknown): string {
86
86
  return date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
87
87
  }
88
88
 
89
+ function entryToCopyableMessage(entry: unknown): CopyableMessage | undefined {
90
+ if (entry === null || typeof entry !== "object") return undefined;
91
+ const record = entry as Record<string, unknown>;
92
+ if (record.type !== "message") return undefined;
93
+ if (record.message === null || typeof record.message !== "object") return undefined;
94
+
95
+ const message = record.message as Record<string, unknown>;
96
+ const role = typeof message.role === "string" ? message.role : "message";
97
+ const text = textFromMessage(message);
98
+ if (!text.trim()) return undefined;
99
+
100
+ return {
101
+ id: typeof record.id === "string" ? record.id : "unknown",
102
+ role,
103
+ timestamp: typeof record.timestamp === "string" ? record.timestamp : undefined,
104
+ text,
105
+ };
106
+ }
107
+
89
108
  export function collectCopyableMessages(ctx: { sessionManager: { getBranch(): unknown[] } }): CopyableMessage[] {
90
- const messages: CopyableMessage[] = [];
109
+ return ctx.sessionManager.getBranch().flatMap((entry) => {
110
+ const message = entryToCopyableMessage(entry);
111
+ return message ? [message] : [];
112
+ });
113
+ }
114
+
115
+ export type MostRecentUserMessageResult =
116
+ | { kind: "message"; message: CopyableMessage }
117
+ | { kind: "no-user-message" }
118
+ | { kind: "no-text" };
91
119
 
92
- for (const entry of ctx.sessionManager.getBranch()) {
120
+ export function getMostRecentUserMessage(ctx: { sessionManager: { getBranch(): unknown[] } }): MostRecentUserMessageResult {
121
+ const branch = ctx.sessionManager.getBranch();
122
+
123
+ for (let i = branch.length - 1; i >= 0; i--) {
124
+ const entry = branch[i];
93
125
  if (entry === null || typeof entry !== "object") continue;
94
126
  const record = entry as Record<string, unknown>;
95
127
  if (record.type !== "message") continue;
96
128
  if (record.message === null || typeof record.message !== "object") continue;
97
129
 
98
130
  const message = record.message as Record<string, unknown>;
99
- const role = typeof message.role === "string" ? message.role : "message";
131
+ if (message.role !== "user") continue;
132
+
100
133
  const text = textFromMessage(message);
101
- if (!text.trim()) continue;
102
-
103
- messages.push({
104
- id: typeof record.id === "string" ? record.id : "unknown",
105
- role,
106
- timestamp: typeof record.timestamp === "string" ? record.timestamp : undefined,
107
- text,
108
- });
134
+ if (!text.trim()) return { kind: "no-text" };
135
+
136
+ return {
137
+ kind: "message",
138
+ message: {
139
+ id: typeof record.id === "string" ? record.id : "unknown",
140
+ role: "user",
141
+ timestamp: typeof record.timestamp === "string" ? record.timestamp : undefined,
142
+ text,
143
+ },
144
+ };
109
145
  }
110
146
 
111
- return messages;
147
+ return { kind: "no-user-message" };
112
148
  }
113
149
 
114
150
  function commandExists(command: string): boolean {
@@ -419,7 +455,28 @@ function copySelectedMessage(ctx: Pick<ExtensionCommandContext, "ui">, selected:
419
455
  ctx.ui.notify(`Copied ${roleLabel(selected.role)} message`, "info");
420
456
  }
421
457
 
458
+ function copyMostRecentUserMessage(ctx: Pick<ExtensionCommandContext, "sessionManager" | "ui">) {
459
+ const result = getMostRecentUserMessage(ctx);
460
+ if (result.kind === "no-user-message") {
461
+ ctx.ui.notify("No user messages found", "warning");
462
+ return;
463
+ }
464
+ if (result.kind === "no-text") {
465
+ ctx.ui.notify("The most recent user message has no text to copy", "warning");
466
+ return;
467
+ }
468
+
469
+ copySelectedMessage(ctx, result.message);
470
+ }
471
+
422
472
  export default function copyMessageExtension(pi: Pick<ExtensionAPI, "registerCommand">) {
473
+ pi.registerCommand("copy-user", {
474
+ description: "Copy the most recent user message to the clipboard",
475
+ handler: async (_args, ctx) => {
476
+ copyMostRecentUserMessage(ctx);
477
+ },
478
+ });
479
+
423
480
  pi.registerCommand("copy-message", {
424
481
  description: "Select a session message and copy its raw text to the clipboard",
425
482
  handler: async (args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-copy-message",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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",
@@ -15,6 +15,7 @@
15
15
  "extension",
16
16
  "clipboard",
17
17
  "copy-message",
18
+ "copy-user",
18
19
  "copy",
19
20
  "session-messages",
20
21
  "slash-command",