pi-copy-message 1.0.11 → 2.0.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 +7 -0
- package/README.md +7 -5
- package/extensions/copy-message.ts +52 -12
- package/package.json +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0 - 2026-08-06
|
|
4
|
+
|
|
5
|
+
- Set pi 0.84.0 as the minimum supported version and refresh the local coding-agent and TUI development baseline.
|
|
6
|
+
- Honor configured `tui.select.pageUp` and `tui.select.pageDown` bindings, with Ctrl-modified page and jump controls that remain available in fullscreen mode.
|
|
7
|
+
- Decode Kitty CSI-u printable input so type-to-filter search works in terminals using Kitty keyboard disambiguation.
|
|
8
|
+
- Validate the package and its core copy flows with an isolated Pi 0.84.0 installation.
|
|
9
|
+
|
|
3
10
|
## 1.0.11 - 2026-07-16
|
|
4
11
|
|
|
5
12
|
- Refresh the local Pi development lock and compatibility baseline to 0.80.9; the unified model runtime and additive Kimi/model-catalog changes do not affect the message-history picker.
|
package/README.md
CHANGED
|
@@ -93,8 +93,10 @@ Copy with role and timestamp metadata instead of raw text only:
|
|
|
93
93
|
|---|---|
|
|
94
94
|
| Configured `tui.select.up` (default: `↑`) | Move to older visible message |
|
|
95
95
|
| Configured `tui.select.down` (default: `↓`) | Move to newer visible message |
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
96
|
+
| Configured `tui.select.pageUp` / `tui.select.pageDown` (default: `PageUp` / `PageDown`) | Move by one visible page in regular mode |
|
|
97
|
+
| `Ctrl+PageUp` / `Ctrl+PageDown` | Move by one visible page in fullscreen mode |
|
|
98
|
+
| `Home` / `End` | Jump to oldest/newest visible message in regular mode |
|
|
99
|
+
| `Ctrl+Home` / `Ctrl+End` | Jump to oldest/newest visible message in fullscreen mode |
|
|
98
100
|
| Type text | Filter visible messages |
|
|
99
101
|
| `time:<term>` | Search timestamps |
|
|
100
102
|
| `Backspace` | Delete one search character |
|
|
@@ -122,8 +124,8 @@ Copy with role and timestamp metadata instead of raw text only:
|
|
|
122
124
|
|
|
123
125
|
## Compatibility
|
|
124
126
|
|
|
125
|
-
-
|
|
126
|
-
- Pi
|
|
127
|
+
- Requires and is tested with Pi 0.84.0 or later
|
|
128
|
+
- Pi includes `Ctrl+X` for copying the latest assistant response; this extension remains useful for searchable history, other roles, metadata, and direct selectors.
|
|
127
129
|
- Supported Node.js range for local repo tooling: `>=22.19.0`
|
|
128
130
|
- `.nvmrc` pins Node 22.19.0 for local development
|
|
129
131
|
|
|
@@ -139,4 +141,4 @@ npm run check
|
|
|
139
141
|
Key files:
|
|
140
142
|
|
|
141
143
|
- `extensions/copy-message.ts` — publishable extension implementation
|
|
142
|
-
- `tests/copy-message.test.ts` — regression tests for command wiring, filtering, search,
|
|
144
|
+
- `tests/copy-message.test.ts` — regression tests for command wiring, filtering, search, navigation, and copy formatting
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import type { ExtensionAPI, ExtensionCommandContext, KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
4
|
-
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { AutocompleteItem, TuiMode } from "@earendil-works/pi-tui";
|
|
4
|
+
import { decodeKittyPrintable, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
5
5
|
|
|
6
6
|
const MAX_VISIBLE_MESSAGES = 8;
|
|
7
7
|
const MAX_PEEK_LINES = 16;
|
|
@@ -370,7 +370,7 @@ function bindingHint(keybindings: PickerKeybindings | undefined, action: "up" |
|
|
|
370
370
|
return keys.reduce((shortest, key) => visibleWidth(key) < visibleWidth(shortest) ? key : shortest, keys[0] ?? "unbound");
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
function helpLines(width: number, keybindings?: PickerKeybindings): string[] {
|
|
373
|
+
function helpLines(width: number, keybindings?: PickerKeybindings, tuiMode: TuiMode = "regular"): string[] {
|
|
374
374
|
const up = bindingHint(keybindings, "up");
|
|
375
375
|
const down = bindingHint(keybindings, "down");
|
|
376
376
|
const confirm = bindingHint(keybindings, "confirm");
|
|
@@ -379,6 +379,8 @@ function helpLines(width: number, keybindings?: PickerKeybindings): string[] {
|
|
|
379
379
|
!keybindings ||
|
|
380
380
|
!(keybindings.matches(data, "tui.select.up") ||
|
|
381
381
|
keybindings.matches(data, "tui.select.down") ||
|
|
382
|
+
keybindings.matches(data, "tui.select.pageUp") ||
|
|
383
|
+
keybindings.matches(data, "tui.select.pageDown") ||
|
|
382
384
|
keybindings.matches(data, "tui.select.confirm") ||
|
|
383
385
|
keybindings.matches(data, "tui.select.cancel"));
|
|
384
386
|
const peek = available("\t") ? "Tab peek" : undefined;
|
|
@@ -389,10 +391,15 @@ function helpLines(width: number, keybindings?: PickerKeybindings): string[] {
|
|
|
389
391
|
].filter(({ data }) => available(data));
|
|
390
392
|
const filterHint = filters.length > 0 ? `Ctrl+${filters.map(({ hint }) => hint).join("/")} filters` : undefined;
|
|
391
393
|
const meta = available("\x1bm") ? "Alt+M meta" : undefined;
|
|
392
|
-
const jumps =
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
const jumps = (tuiMode === "fullscreen"
|
|
395
|
+
? [
|
|
396
|
+
{ hint: "Ctrl+Home", data: "\x1b[1;5H" },
|
|
397
|
+
{ hint: "Ctrl+End", data: "\x1b[1;5F" },
|
|
398
|
+
]
|
|
399
|
+
: [
|
|
400
|
+
{ hint: "Home", data: "\x1b[H" },
|
|
401
|
+
{ hint: "End", data: "\x1b[F" },
|
|
402
|
+
]).filter(({ data }) => available(data));
|
|
396
403
|
const jumpHint = jumps.length > 0 ? `${jumps.map(({ hint }) => hint).join("/")} jump` : undefined;
|
|
397
404
|
const join = (...hints: Array<string | undefined>) => hints.filter(Boolean).join(" · ");
|
|
398
405
|
const core = width < 74 ? [`${up}/${down} nav`, `${confirm} copy`, `${cancel} cancel`] : [`${up} older`, `${down} newer`, `${confirm} copy`, `${cancel} cancel`];
|
|
@@ -445,7 +452,7 @@ export class CopyMessagePickerState {
|
|
|
445
452
|
return selected ? formatMessageForCopy(selected, this.format) : undefined;
|
|
446
453
|
}
|
|
447
454
|
|
|
448
|
-
render(width: number, theme: CopyMessageTheme, keybindings?: PickerKeybindings): string[] {
|
|
455
|
+
render(width: number, theme: CopyMessageTheme, keybindings?: PickerKeybindings, tuiMode: TuiMode = "regular"): string[] {
|
|
449
456
|
const maxVisible = Math.min(this.visibleMessages.length, MAX_VISIBLE_MESSAGES);
|
|
450
457
|
const start = maxVisible === 0 ? 0 : Math.max(0, Math.min(this.selectedIndex - maxVisible + 1, this.visibleMessages.length - maxVisible));
|
|
451
458
|
const end = Math.min(this.visibleMessages.length, start + maxVisible);
|
|
@@ -476,7 +483,7 @@ export class CopyMessagePickerState {
|
|
|
476
483
|
const position = this.visibleMessages.length === 0 ? "0/0" : `${this.selectedIndex + 1}/${this.visibleMessages.length}`;
|
|
477
484
|
lines.push(`${theme.fg("dim", `(${position})`)} · ${userState} · ${assistantState} · ${toolState} · ${formatState} · ${searchState}`);
|
|
478
485
|
lines.push("");
|
|
479
|
-
lines.push(...helpLines(width, keybindings).map((line) => hotkeyHint(theme, line)));
|
|
486
|
+
lines.push(...helpLines(width, keybindings, tuiMode).map((line) => hotkeyHint(theme, line)));
|
|
480
487
|
lines.push("");
|
|
481
488
|
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
482
489
|
}
|
|
@@ -490,6 +497,14 @@ export class CopyMessagePickerState {
|
|
|
490
497
|
this.move(1);
|
|
491
498
|
return "render";
|
|
492
499
|
}
|
|
500
|
+
if (keybindings?.matches(data, "tui.select.pageUp")) {
|
|
501
|
+
this.move(-MAX_VISIBLE_MESSAGES);
|
|
502
|
+
return "render";
|
|
503
|
+
}
|
|
504
|
+
if (keybindings?.matches(data, "tui.select.pageDown")) {
|
|
505
|
+
this.move(MAX_VISIBLE_MESSAGES);
|
|
506
|
+
return "render";
|
|
507
|
+
}
|
|
493
508
|
if (keybindings?.matches(data, "tui.select.confirm")) {
|
|
494
509
|
return this.visibleMessages.length > 0 ? "copy" : "none";
|
|
495
510
|
}
|
|
@@ -521,8 +536,9 @@ export class CopyMessagePickerState {
|
|
|
521
536
|
this.setSearch(this.search.slice(0, -1));
|
|
522
537
|
return "render";
|
|
523
538
|
}
|
|
524
|
-
|
|
525
|
-
|
|
539
|
+
const printable = decodeKittyPrintable(data) ?? data;
|
|
540
|
+
if (isPrintableSearchInput(printable)) {
|
|
541
|
+
this.setSearch(this.search + printable);
|
|
526
542
|
return "render";
|
|
527
543
|
}
|
|
528
544
|
if (!keybindings && matchesKey(data, "up")) {
|
|
@@ -533,6 +549,30 @@ export class CopyMessagePickerState {
|
|
|
533
549
|
this.move(1);
|
|
534
550
|
return "render";
|
|
535
551
|
}
|
|
552
|
+
if (!keybindings && matchesKey(data, "pageUp")) {
|
|
553
|
+
this.move(-MAX_VISIBLE_MESSAGES);
|
|
554
|
+
return "render";
|
|
555
|
+
}
|
|
556
|
+
if (!keybindings && matchesKey(data, "pageDown")) {
|
|
557
|
+
this.move(MAX_VISIBLE_MESSAGES);
|
|
558
|
+
return "render";
|
|
559
|
+
}
|
|
560
|
+
if (matchesKey(data, "ctrl+pageUp")) {
|
|
561
|
+
this.move(-MAX_VISIBLE_MESSAGES);
|
|
562
|
+
return "render";
|
|
563
|
+
}
|
|
564
|
+
if (matchesKey(data, "ctrl+pageDown")) {
|
|
565
|
+
this.move(MAX_VISIBLE_MESSAGES);
|
|
566
|
+
return "render";
|
|
567
|
+
}
|
|
568
|
+
if (matchesKey(data, "ctrl+home")) {
|
|
569
|
+
this.jumpToTop();
|
|
570
|
+
return "render";
|
|
571
|
+
}
|
|
572
|
+
if (matchesKey(data, "ctrl+end")) {
|
|
573
|
+
this.jumpToBottom();
|
|
574
|
+
return "render";
|
|
575
|
+
}
|
|
536
576
|
if (matchesKey(data, "home")) {
|
|
537
577
|
this.jumpToTop();
|
|
538
578
|
return "render";
|
|
@@ -631,7 +671,7 @@ async function pickMessage(ctx: ExtensionCommandContext, messages: CopyableMessa
|
|
|
631
671
|
const state = new CopyMessagePickerState(messages, initialFormat);
|
|
632
672
|
return {
|
|
633
673
|
render(width: number) {
|
|
634
|
-
return state.render(width, theme, keybindings);
|
|
674
|
+
return state.render(width, theme, keybindings, tui.mode);
|
|
635
675
|
},
|
|
636
676
|
invalidate() {},
|
|
637
677
|
handleInput(data: string) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-copy-message",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"LICENSE"
|
|
45
45
|
],
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "
|
|
48
|
-
"@earendil-works/pi-tui": "
|
|
47
|
+
"@earendil-works/pi-coding-agent": "0.84.0",
|
|
48
|
+
"@earendil-works/pi-tui": "0.84.0",
|
|
49
49
|
"@types/node": "^25.9.1",
|
|
50
50
|
"tsx": "^4.22.4",
|
|
51
51
|
"typescript": "^6.0.3"
|
|
@@ -59,5 +59,13 @@
|
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@earendil-works/pi-coding-agent": "*",
|
|
61
61
|
"@earendil-works/pi-tui": "*"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@earendil-works/pi-coding-agent": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@earendil-works/pi-tui": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
62
70
|
}
|
|
63
71
|
}
|