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 +4 -0
- package/README.md +8 -1
- package/extensions/copy-message.ts +69 -12
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
if (message.role !== "user") continue;
|
|
132
|
+
|
|
100
133
|
const text = textFromMessage(message);
|
|
101
|
-
if (!text.trim())
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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.
|
|
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",
|