pi-extensions 0.1.31 → 0.1.32

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/README.md CHANGED
@@ -8,7 +8,7 @@ Personal extensions for the [Pi coding agent](https://github.com/badlogic/pi-mon
8
8
  |-----------|-------------|
9
9
  | [/readfiles](files-widget/) | In-terminal file browser and viewer widget. Navigate files, view diffs, select code, send comments to agent - without leaving Pi, and without interrupting your agent |
10
10
  | [tab-status](tab-status/) | Manage as many parallel sessions as your mind can handle. Terminal tab indicators for <br>✅ done / 🚧 stuck / 🛑 timed out |
11
- | [ralph-wiggum](ralph-wiggum/) | Run arbitrarily-long tasks without diluting model attention. Flat version without subagents like [ralph-loop](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/ralph-loop) |
11
+ | [pi-ralph-wiggum](pi-ralph-wiggum/) | Run arbitrarily-long tasks without diluting model attention. Flat version without subagents like [ralph-loop](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/ralph-loop) |
12
12
  | [agent-guidance](agent-guidance/) | Switch between Claude/Codex/Gemini with model-specific guidance (CLAUDE.md, CODEX.md, GEMINI.md) |
13
13
  | [/usage](usage-extension/) | 📊 Usage statistics dashboard. See cost, tokens, and messages by provider/model across Today, This Week, Last Week, and All Time — with a compact view for narrow terminals |
14
14
  | [/paste](raw-paste/) | Paste editable text, not [paste #1 +21 lines]. Running `/paste` with optional keybinding |
@@ -21,7 +21,7 @@ Personal extensions for the [Pi coding agent](https://github.com/badlogic/pi-mon
21
21
  |-------|-------------|
22
22
  | [extending-pi](extending-pi/) | Guide for extending Pi — decide between skills, extensions, prompt templates, themes, or packages. |
23
23
  | ↳ [skill-creator](extending-pi/skill-creator/) | Detailed guidance for creating Pi skills. |
24
- | [ralph-wiggum](ralph-wiggum/) | Skill instructions for long-running development loops. |
24
+ | [pi-ralph-wiggum](pi-ralph-wiggum/) | Skill instructions for long-running development loops. |
25
25
 
26
26
  ## Install (pi package manager)
27
27
 
@@ -56,7 +56,7 @@ If you keep a local clone, add extensions to your `~/.pi/agent/settings.json`:
56
56
  "~/pi-extensions/arcade/picman.ts",
57
57
  "~/pi-extensions/arcade/tetris.ts",
58
58
  "~/pi-extensions/arcade/mario-not/mario-not.ts",
59
- "~/pi-extensions/ralph-wiggum",
59
+ "~/pi-extensions/pi-ralph-wiggum",
60
60
  "~/pi-extensions/agent-guidance/agent-guidance.ts",
61
61
  "~/pi-extensions/raw-paste",
62
62
  "~/pi-extensions/code-actions",
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this extension will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ### Changed
8
+ - Make the inline comment editor multiline with wrapped footer rendering, `Enter` for a new line, and `Ctrl+Enter`/`Ctrl+D` to send.
9
+
5
10
  ## [0.1.16] - 2026-04-19
6
11
 
7
12
  ### Fixed
@@ -117,6 +117,8 @@ If missing, `/review` or `/diff` will show a clear install prompt.
117
117
  - `n` / `N`: next/prev match
118
118
  - `v`: select mode (line selection)
119
119
  - `c`: comment on selected lines (inline prompt)
120
+ - `Enter`: new line in the comment editor
121
+ - `Ctrl+Enter` or `Ctrl+D`: send the comment (`Alt+Enter` also works when supported)
120
122
  - `]` / `[`: next/prev changed file
121
123
  - `+` / `-`: increase/decrease viewer height
122
124
  - `q`: back to browser
@@ -66,8 +66,8 @@
66
66
 
67
67
  ### Comment Dialog
68
68
  - [x] `c` to open comment input
69
- - [ ] Multi-line text input
70
- - [x] `Enter` or `Ctrl+Enter` to confirm
69
+ - [x] Multi-line text input
70
+ - [x] `Enter` for newline and `Ctrl+Enter` / `Ctrl+D` to confirm
71
71
  - [x] `Esc` to cancel
72
72
 
73
73
  ### Send to Agent
@@ -4,17 +4,22 @@ const CONTROL_CHARS = /[\u0000-\u0008\u000B-\u001F\u007F]/g;
4
4
  const BRACKETED_PASTE_START = "\u001b[200~";
5
5
  const BRACKETED_PASTE_END = "\u001b[201~";
6
6
 
7
- function sanitizeTextInput(data: string): string {
7
+ interface SanitizeTextInputOptions {
8
+ preserveNewlines?: boolean;
9
+ }
10
+
11
+ function sanitizeTextInput(data: string, options: SanitizeTextInputOptions = {}): string {
8
12
  const normalized = decodeKittyPrintable(data) ?? data;
9
13
  if (!normalized || normalized.includes("\u001b")) {
10
14
  return "";
11
15
  }
12
16
 
13
- return normalized
14
- .replace(/\r\n?/g, "\n")
15
- .replace(/\n/g, " ")
16
- .replace(/\t/g, " ")
17
- .replace(CONTROL_CHARS, "");
17
+ const withNewlines = normalized.replace(/\r\n?/g, "\n");
18
+ const withoutTabs = options.preserveNewlines
19
+ ? withNewlines.replace(/\t/g, " ")
20
+ : withNewlines.replace(/\n/g, " ").replace(/\t/g, " ");
21
+
22
+ return withoutTabs.replace(CONTROL_CHARS, "");
18
23
  }
19
24
 
20
25
  function getPendingStartSuffix(data: string): string {
@@ -33,7 +38,11 @@ export interface TextInputBuffer {
33
38
  reset(): void;
34
39
  }
35
40
 
36
- export function createTextInputBuffer(): TextInputBuffer {
41
+ interface TextInputBufferOptions {
42
+ preserveNewlines?: boolean;
43
+ }
44
+
45
+ export function createTextInputBuffer(options: TextInputBufferOptions = {}): TextInputBuffer {
37
46
  let isInPaste = false;
38
47
  let pasteBuffer = "";
39
48
  let pendingStart = "";
@@ -52,14 +61,14 @@ export function createTextInputBuffer(): TextInputBuffer {
52
61
  const pendingSuffix = getPendingStartSuffix(combined);
53
62
  const completeText = pendingSuffix ? combined.slice(0, combined.length - pendingSuffix.length) : combined;
54
63
  pendingStart = pendingSuffix;
55
- return sanitizeTextInput(completeText);
64
+ return sanitizeTextInput(completeText, options);
56
65
  }
57
66
 
58
67
  const beforePaste = combined.slice(0, startIndex);
59
68
  const afterStart = combined.slice(startIndex + BRACKETED_PASTE_START.length);
60
69
  isInPaste = true;
61
70
  pasteBuffer = "";
62
- return sanitizeTextInput(beforePaste) + push(afterStart);
71
+ return sanitizeTextInput(beforePaste, options) + push(afterStart);
63
72
  }
64
73
 
65
74
  pasteBuffer += combined;
@@ -73,7 +82,7 @@ export function createTextInputBuffer(): TextInputBuffer {
73
82
  isInPaste = false;
74
83
  pasteBuffer = "";
75
84
 
76
- return sanitizeTextInput(pastedText) + push(remaining);
85
+ return sanitizeTextInput(pastedText, options) + push(remaining);
77
86
  };
78
87
 
79
88
  return {
@@ -1,5 +1,5 @@
1
1
  import type { Theme } from "@mariozechner/pi-coding-agent";
2
- import { Key, matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
2
+ import { Key, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
3
3
  import { readFileSync, statSync } from "node:fs";
4
4
  import { relative } from "node:path";
5
5
 
@@ -14,6 +14,8 @@ import type { FileNode } from "./types";
14
14
  import { isUntrackedStatus } from "./utils";
15
15
  import { createTextInputBuffer } from "./input-utils";
16
16
 
17
+ const COMMENT_EDITOR_MAX_VISIBLE_LINES = 4;
18
+
17
19
  export interface CommentPayload {
18
20
  relPath: string;
19
21
  lineRange: string;
@@ -61,7 +63,8 @@ export function createViewer(
61
63
  theme: Theme,
62
64
  requestComment: (payload: CommentPayload, comment: string) => void
63
65
  ): ViewerController {
64
- const textInput = createTextInputBuffer();
66
+ const searchInput = createTextInputBuffer();
67
+ const commentInput = createTextInputBuffer({ preserveNewlines: true });
65
68
 
66
69
  const state: ViewerState = {
67
70
  file: null,
@@ -98,7 +101,8 @@ export function createViewer(
98
101
 
99
102
  function setMode(mode: ViewerMode): void {
100
103
  if (mode !== state.mode) {
101
- textInput.reset();
104
+ searchInput.reset();
105
+ commentInput.reset();
102
106
  }
103
107
 
104
108
  state.mode = mode;
@@ -264,6 +268,38 @@ export function createViewer(
264
268
  return truncateToWidth(header, width);
265
269
  }
266
270
 
271
+ function renderCommentEditor(width: number): string[] {
272
+ const contentWidth = Math.max(1, width - 2);
273
+ const wrappedLines: string[] = [];
274
+ const logicalLines = state.commentText.split("\n");
275
+
276
+ for (const line of logicalLines) {
277
+ if (line.length === 0) {
278
+ wrappedLines.push("");
279
+ continue;
280
+ }
281
+ wrappedLines.push(...wrapTextWithAnsi(line, contentWidth));
282
+ }
283
+
284
+ if (wrappedLines.length === 0) {
285
+ wrappedLines.push("");
286
+ }
287
+
288
+ const lastIndex = wrappedLines.length - 1;
289
+ wrappedLines[lastIndex] = `${wrappedLines[lastIndex]}█`;
290
+
291
+ const overflow = Math.max(0, wrappedLines.length - COMMENT_EDITOR_MAX_VISIBLE_LINES);
292
+ const visibleLines = wrappedLines.slice(-COMMENT_EDITOR_MAX_VISIBLE_LINES);
293
+ if (overflow > 0 && visibleLines.length > 0) {
294
+ visibleLines[0] = `…${visibleLines[0]}`;
295
+ }
296
+
297
+ return [
298
+ truncateToWidth(theme.fg("accent", "Comment:"), width),
299
+ ...visibleLines.map(line => truncateToWidth(` ${line}`, width)),
300
+ ];
301
+ }
302
+
267
303
  function renderFooter(width: number): string[] {
268
304
  const lines: string[] = [];
269
305
  const pct = state.content.length > 0
@@ -271,14 +307,13 @@ export function createViewer(
271
307
  : 0;
272
308
 
273
309
  if (state.mode === "comment") {
274
- const prompt = theme.fg("accent", `Comment: ${state.commentText}█`);
275
- lines.push(truncateToWidth(prompt, width));
310
+ lines.push(...renderCommentEditor(width));
276
311
  lines.push(theme.fg("borderMuted", "─".repeat(width)));
277
312
  }
278
313
 
279
314
  let help: string;
280
315
  if (state.mode === "comment") {
281
- help = theme.fg("dim", "Enter: send Esc: cancel");
316
+ help = theme.fg("dim", "Enter: newline Ctrl+Enter/Ctrl+D: send Esc: cancel");
282
317
  } else if (state.mode === "select") {
283
318
  help = theme.fg("dim", "j/k: extend c: comment Esc: cancel");
284
319
  } else if (state.mode === "search") {
@@ -363,19 +398,21 @@ export function createViewer(
363
398
  if (!state.file) return { type: "none" };
364
399
 
365
400
  if (state.mode === "comment") {
366
- if (matchesKey(data, Key.enter)) {
401
+ if (matchesKey(data, "ctrl+enter") || matchesKey(data, "ctrl+d") || matchesKey(data, "alt+enter")) {
367
402
  const comment = state.commentText.trim();
368
403
  if (comment) {
369
404
  sendComment(comment);
370
405
  } else {
371
406
  setMode("normal");
372
407
  }
408
+ } else if (matchesKey(data, Key.enter) || matchesKey(data, "shift+enter")) {
409
+ state.commentText += "\n";
373
410
  } else if (matchesKey(data, Key.escape)) {
374
411
  setMode("normal");
375
412
  } else if (matchesKey(data, Key.backspace)) {
376
413
  state.commentText = state.commentText.slice(0, -1);
377
414
  } else {
378
- const text = textInput.push(data);
415
+ const text = commentInput.push(data);
379
416
  if (text) {
380
417
  state.commentText += text;
381
418
  }
@@ -392,7 +429,7 @@ export function createViewer(
392
429
  state.searchQuery = state.searchQuery.slice(0, -1);
393
430
  updateSearchMatches();
394
431
  } else {
395
- const text = textInput.push(data);
432
+ const text = searchInput.push(data);
396
433
  if (text) {
397
434
  state.searchQuery += text;
398
435
  updateSearchMatches();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extensions",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "keywords": [
@@ -17,14 +17,14 @@
17
17
  "./code-actions/index.ts",
18
18
  "./files-widget/index.ts",
19
19
  "./raw-paste/index.ts",
20
- "./ralph-wiggum/index.ts",
20
+ "./pi-ralph-wiggum/index.ts",
21
21
  "./tab-status/tab-status.ts",
22
22
  "./usage-extension/index.ts"
23
23
  ],
24
24
  "skills": [
25
25
  "./extending-pi/SKILL.md",
26
26
  "./extending-pi/skill-creator/SKILL.md",
27
- "./ralph-wiggum/SKILL.md"
27
+ "./pi-ralph-wiggum/SKILL.md"
28
28
  ]
29
29
  }
30
30
  }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2026-04-19
4
+
5
+ ### Changed
6
+ - **BREAKING:** SKILL.md `name` renamed `ralph-wiggum` → `pi-ralph-wiggum` to match the parent directory (both in the repo and after `pi install npm:@tmustier/pi-ralph-wiggum`). This removes the `[Skill conflicts]` warning pi emitted on every startup, but it also changes the skill's public identifier — explicit invocations must now use `/skill:pi-ralph-wiggum` instead of `/skill:ralph-wiggum`. Thanks to @ishanmalik for reporting ([#12](https://github.com/tmustier/pi-extensions/issues/12)).
7
+ - Repo directory renamed `ralph-wiggum/` → `pi-ralph-wiggum/` as part of the same fix. Git-source users referencing `~/pi-extensions/ralph-wiggum/…` in their pi config should update the path to `~/pi-extensions/pi-ralph-wiggum/…`. The npm package name (`@tmustier/pi-ralph-wiggum`) is unchanged.
8
+ - Renamed the README's `Install` section to `Installation` so it matches the skill validator's expectations.
9
+
3
10
  ## 0.1.7 - 2026-04-19
4
11
 
5
12
  ### Fixed
@@ -10,7 +10,7 @@ This one is cool because:
10
10
 
11
11
  **Note: This is a flat version without subagents, similar to the [Anthropic plugins implementation](https://github.com/anthropics/claude-code-plugins/tree/main/ralph-loop).**
12
12
 
13
- ## Install
13
+ ## Installation
14
14
 
15
15
  ```bash
16
16
  pi install npm:@tmustier/pi-ralph-wiggum
@@ -27,8 +27,8 @@ Then filter to just this extension in `~/.pi/agent/settings.json`:
27
27
  "packages": [
28
28
  {
29
29
  "source": "git:github.com/tmustier/pi-extensions",
30
- "extensions": ["ralph-wiggum/index.ts"],
31
- "skills": ["ralph-wiggum/SKILL.md"]
30
+ "extensions": ["pi-ralph-wiggum/index.ts"],
31
+ "skills": ["pi-ralph-wiggum/SKILL.md"]
32
32
  }
33
33
  ]
34
34
  }
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ralph-wiggum
2
+ name: pi-ralph-wiggum
3
3
  description: Long-running iterative development loops with pacing control and verifiable progress. Use when tasks require multiple iterations, many discrete steps, or periodic reflection with clear checkpoints; avoid for simple one-shot tasks or quick fixes.
4
4
  ---
5
5
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-ralph-wiggum",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "Long-running agent loops for iterative development in Pi.",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Mustier",
@@ -10,10 +10,10 @@
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/tmustier/pi-extensions.git",
13
- "directory": "ralph-wiggum"
13
+ "directory": "pi-ralph-wiggum"
14
14
  },
15
15
  "bugs": "https://github.com/tmustier/pi-extensions/issues",
16
- "homepage": "https://github.com/tmustier/pi-extensions/tree/main/ralph-wiggum",
16
+ "homepage": "https://github.com/tmustier/pi-extensions/tree/main/pi-ralph-wiggum",
17
17
  "pi": {
18
18
  "extensions": [
19
19
  "index.ts"
File without changes
File without changes