pi-copy-message 1.0.7 → 1.0.8

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.8 - 2026-06-24
4
+
5
+ - Add `/copy-message` and `/copy-user` argument completions for `latest`/`last`/`newest` and `--with-meta`/`--with-metadata`/`--with-role`.
6
+ - Bound clipboard commands with a 3s timeout so a wedged `xclip`/`wl-copy` daemon cannot hang the command.
7
+ - Drop the obsolete `basic-ftp` npm override (no longer in the dependency tree).
8
+ - Align pi core peer dependencies with package guidance (non-optional wildcard peers; pi aliases them to its bundled copies at load time).
9
+ - Update the local pi development baseline to `0.80.2` and refresh the npm lockfile.
10
+
3
11
  ## 1.0.7 - 2026-06-23
4
12
 
5
13
  - updated the local pi development baseline to `@earendil-works/pi-coding-agent` / `@earendil-works/pi-tui` `0.80.1` and refreshed the npm lockfile
package/README.md CHANGED
@@ -119,11 +119,11 @@ Copy with role and timestamp metadata instead of raw text only:
119
119
 
120
120
  ## Compatibility
121
121
 
122
- - Tested with pi 0.80.1
122
+ - Tested with pi 0.80.2
123
123
  - Supported Node.js range for local repo tooling: `>=22.19.0`
124
124
  - `.nvmrc` pins Node 22.19.0 for local development
125
125
 
126
- This package keeps pi core packages as optional wildcard peers per current pi package guidance. Local development uses `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` as dev dependencies for typechecking and tests.
126
+ This package keeps pi core packages as wildcard peers (`*`) per pi package guidance. Pi aliases these imports to its own bundled copies when loading the extension, so the package never bundles or shadows pi core. Local development uses `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` as dev dependencies for typechecking and tests.
127
127
 
128
128
  ## Development
129
129
 
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
3
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
3
4
  import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
4
5
 
5
6
  const MAX_VISIBLE_MESSAGES = 8;
@@ -183,20 +184,25 @@ const clipboardCommands: ClipboardCommand[] = [
183
184
  { name: "xsel", args: ["--clipboard", "--input"], enabled: () => true },
184
185
  ];
185
186
 
187
+ const CLIPBOARD_TIMEOUT_MS = 3000;
186
188
  const commandExistsCache = new Map<string, boolean>();
187
189
 
188
190
  function commandExists(command: string): boolean {
189
191
  const cached = commandExistsCache.get(command);
190
192
  if (cached !== undefined) return cached;
191
193
 
192
- const result = spawnSync("sh", ["-c", "command -v \"$1\" >/dev/null 2>&1", "sh", command], { stdio: "ignore" });
194
+ const result = spawnSync("sh", ["-c", "command -v \"$1\" >/dev/null 2>&1", "sh", command], {
195
+ stdio: "ignore",
196
+ timeout: CLIPBOARD_TIMEOUT_MS,
197
+ killSignal: "SIGKILL",
198
+ });
193
199
  const exists = result.status === 0;
194
200
  commandExistsCache.set(command, exists);
195
201
  return exists;
196
202
  }
197
203
 
198
204
  function copyWith(command: string, args: string[], text: string): boolean {
199
- const result = spawnSync(command, args, { input: text, encoding: "utf8" });
205
+ const result = spawnSync(command, args, { input: text, encoding: "utf8", timeout: CLIPBOARD_TIMEOUT_MS, killSignal: "SIGKILL" });
200
206
  return !result.error && result.status === 0;
201
207
  }
202
208
 
@@ -513,6 +519,9 @@ export class CopyMessagePickerState {
513
519
  }
514
520
  }
515
521
 
522
+ const COPY_METADATA_FLAGS = ["--with-meta", "--with-metadata", "--with-role"];
523
+ const COPY_LATEST_SELECTORS = ["latest", "last", "newest"];
524
+
516
525
  type ParsedCopyMessageArgs = {
517
526
  format: CopyFormat;
518
527
  selector?: "latest" | { number: number };
@@ -521,11 +530,11 @@ type ParsedCopyMessageArgs = {
521
530
  function parseCopyArgs(args: string | undefined): ParsedCopyMessageArgs {
522
531
  const result: ParsedCopyMessageArgs = { format: "raw" };
523
532
  for (const token of (args ?? "").trim().toLowerCase().split(/\s+/).filter(Boolean)) {
524
- if (token === "--with-meta" || token === "--with-metadata" || token === "--with-role") {
533
+ if (COPY_METADATA_FLAGS.includes(token)) {
525
534
  result.format = "metadata";
526
535
  continue;
527
536
  }
528
- if (token === "last" || token === "latest" || token === "newest") {
537
+ if (COPY_LATEST_SELECTORS.includes(token)) {
529
538
  result.selector = "latest";
530
539
  continue;
531
540
  }
@@ -536,6 +545,16 @@ function parseCopyArgs(args: string | undefined): ParsedCopyMessageArgs {
536
545
  return result;
537
546
  }
538
547
 
548
+ export function copyArgumentCompletions(prefix: string, includeSelectors: boolean): AutocompleteItem[] | null {
549
+ if (/^\d+$/.test(prefix)) return null;
550
+ const candidates = includeSelectors ? [...COPY_LATEST_SELECTORS, ...COPY_METADATA_FLAGS] : COPY_METADATA_FLAGS;
551
+ const normalized = prefix.toLowerCase();
552
+ const items = candidates
553
+ .filter((candidate) => candidate.startsWith(normalized))
554
+ .map((candidate) => ({ value: candidate, label: candidate }));
555
+ return items.length > 0 ? items : null;
556
+ }
557
+
539
558
  async function pickMessage(ctx: ExtensionCommandContext, messages: CopyableMessage[], initialFormat: CopyFormat) {
540
559
  return ctx.ui.custom<{ message: CopyableMessage; text: string } | null>((tui, theme, _keybindings, done) => {
541
560
  const state = new CopyMessagePickerState(messages, initialFormat);
@@ -593,6 +612,7 @@ function copyMostRecentUserMessage(ctx: Pick<ExtensionCommandContext, "sessionMa
593
612
  export default function copyMessageExtension(pi: Pick<ExtensionAPI, "registerCommand">) {
594
613
  pi.registerCommand("copy-message", {
595
614
  description: "Select a session message and copy its text to the clipboard",
615
+ getArgumentCompletions: (argumentPrefix) => copyArgumentCompletions(argumentPrefix, true),
596
616
  handler: async (args, ctx) => {
597
617
  const parsedArgs = parseCopyArgs(args);
598
618
  const messages = collectCopyableMessages(ctx);
@@ -631,6 +651,7 @@ export default function copyMessageExtension(pi: Pick<ExtensionAPI, "registerCom
631
651
 
632
652
  pi.registerCommand("copy-user", {
633
653
  description: "Copy the most recent user message to the clipboard",
654
+ getArgumentCompletions: (argumentPrefix) => copyArgumentCompletions(argumentPrefix, false),
634
655
  handler: async (args, ctx) => {
635
656
  copyMostRecentUserMessage(ctx, parseCopyArgs(args).format);
636
657
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-copy-message",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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,15 +44,12 @@
44
44
  "LICENSE"
45
45
  ],
46
46
  "devDependencies": {
47
- "@earendil-works/pi-coding-agent": "^0.80.1",
48
- "@earendil-works/pi-tui": "^0.80.1",
47
+ "@earendil-works/pi-coding-agent": "^0.80.2",
48
+ "@earendil-works/pi-tui": "^0.80.2",
49
49
  "@types/node": "^25.9.1",
50
50
  "tsx": "^4.22.4",
51
51
  "typescript": "^6.0.3"
52
52
  },
53
- "overrides": {
54
- "basic-ftp": "6.0.1"
55
- },
56
53
  "pi": {
57
54
  "extensions": [
58
55
  "./extensions"
@@ -62,13 +59,5 @@
62
59
  "peerDependencies": {
63
60
  "@earendil-works/pi-coding-agent": "*",
64
61
  "@earendil-works/pi-tui": "*"
65
- },
66
- "peerDependenciesMeta": {
67
- "@earendil-works/pi-coding-agent": {
68
- "optional": true
69
- },
70
- "@earendil-works/pi-tui": {
71
- "optional": true
72
- }
73
62
  }
74
63
  }