@aliou/pi-processes 0.9.0 → 0.9.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.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/package.json +18 -17
  3. package/src/commands/clear/command.ts +1 -1
  4. package/src/commands/dock/command.ts +1 -1
  5. package/src/commands/index.ts +1 -1
  6. package/src/commands/kill/command.ts +1 -1
  7. package/src/commands/logs/command.ts +1 -1
  8. package/src/commands/pick-process.ts +1 -1
  9. package/src/commands/pin/command.ts +1 -1
  10. package/src/commands/processes/command.ts +1 -1
  11. package/src/commands/settings/command.ts +1 -1
  12. package/src/components/log-dock-component.test.ts +29 -0
  13. package/src/components/log-dock-component.ts +37 -28
  14. package/src/components/log-file-viewer.ts +2 -2
  15. package/src/components/log-overlay-component.ts +2 -2
  16. package/src/components/panel-helpers.ts +37 -0
  17. package/src/components/process-picker-component.ts +8 -9
  18. package/src/components/processes-component.ts +9 -10
  19. package/src/hooks/background-blocker.ts +1 -1
  20. package/src/hooks/cleanup.ts +1 -1
  21. package/src/hooks/index.ts +1 -1
  22. package/src/hooks/message-renderer.ts +2 -2
  23. package/src/hooks/process-end.ts +1 -1
  24. package/src/hooks/process-watch.ts +1 -1
  25. package/src/hooks/utils.ts +1 -1
  26. package/src/hooks/widget/setup.ts +1 -1
  27. package/src/hooks/widget/status-widget.ts +2 -2
  28. package/src/index.ts +1 -1
  29. package/src/manager.ts +2 -2
  30. package/src/tools/actions/debug.ts +1 -1
  31. package/src/tools/actions/index.ts +2 -2
  32. package/src/tools/actions/kill.ts +1 -1
  33. package/src/tools/actions/list.ts +2 -2
  34. package/src/tools/actions/logs.ts +2 -2
  35. package/src/tools/actions/output.ts +2 -2
  36. package/src/tools/actions/start.ts +2 -2
  37. package/src/tools/actions/write.ts +1 -1
  38. package/src/tools/index.ts +3 -3
  39. package/src/utils/ansi.test.ts +22 -0
  40. package/src/utils/ansi.ts +28 -19
  41. package/src/utils/format.ts +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![banner](https://assets.aliou.me/pi-extensions/banners/pi-processes.png)
1
+ ![banner](https://assets.aliou.me/github/aliou/pi-processes/banner.png)
2
2
 
3
3
  # pi-processes
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-processes",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -21,7 +21,7 @@
21
21
  "skills": [
22
22
  "./skills/pi-processes"
23
23
  ],
24
- "video": "https://assets.aliou.me/pi-extensions/demos/pi-processes.mp4"
24
+ "video": "https://assets.aliou.me/github/aliou/pi-processes/demo.mp4"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"
@@ -33,31 +33,32 @@
33
33
  "CONTRIBUTING.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@aliou/pi-utils-settings": "^0.10.0",
37
- "@aliou/pi-utils-ui": "^0.1.0",
36
+ "@aliou/pi-utils-settings": "^0.15.1",
37
+ "@aliou/pi-utils-ui": "^0.4.1",
38
38
  "@aliou/sh": "^0.1.0",
39
- "typebox": "^1.0.0"
39
+ "typebox": "^1.1.38"
40
40
  },
41
41
  "peerDependencies": {
42
- "@mariozechner/pi-coding-agent": "0.72.1",
43
- "@mariozechner/pi-tui": "0.72.1"
42
+ "@earendil-works/pi-coding-agent": "^0.75.3",
43
+ "@earendil-works/pi-tui": "^0.75.3"
44
44
  },
45
45
  "devDependencies": {
46
- "@aliou/biome-plugins": "^0.3.2",
47
- "@biomejs/biome": "^2.3.13",
48
- "@changesets/cli": "^2.27.11",
49
- "@mariozechner/pi-ai": "0.72.1",
50
- "@mariozechner/pi-coding-agent": "0.72.1",
51
- "@types/node": "^25.0.10",
46
+ "@aliou/biome-plugins": "^0.8.1",
47
+ "@biomejs/biome": "^2.4.15",
48
+ "@changesets/cli": "^2.31.0",
49
+ "@types/node": "^25.9.1",
52
50
  "husky": "^9.1.7",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.0.18"
51
+ "typescript": "^6.0.3",
52
+ "vitest": "^4.1.7",
53
+ "@earendil-works/pi-ai": "^0.75.3",
54
+ "@earendil-works/pi-coding-agent": "^0.75.3",
55
+ "@earendil-works/pi-tui": "^0.75.3"
55
56
  },
56
57
  "peerDependenciesMeta": {
57
- "@mariozechner/pi-coding-agent": {
58
+ "@earendil-works/pi-coding-agent": {
58
59
  "optional": true
59
60
  },
60
- "@mariozechner/pi-tui": {
61
+ "@earendil-works/pi-tui": {
61
62
  "optional": true
62
63
  }
63
64
  },
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessManager } from "../../manager";
3
3
 
4
4
  export function registerPsClearCommand(
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { DockActions } from "../../hooks/widget";
3
3
 
4
4
  export function registerPsDockCommand(
@@ -9,7 +9,7 @@
9
9
  * /ps:dock [show|hide|toggle] - Control dock visibility
10
10
  */
11
11
 
12
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
  import type { DockActions } from "../hooks/widget";
14
14
  import type { ProcessManager } from "../manager";
15
15
  import { registerPsClearCommand } from "./clear";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { LIVE_STATUSES } from "../../constants";
3
3
  import type { DockActions } from "../../hooks/widget";
4
4
  import type { ProcessManager } from "../../manager";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { LogOverlayComponent } from "../../components/log-overlay-component";
3
3
  import type { ProcessManager } from "../../manager";
4
4
  import { allProcessCompletions } from "../completions";
@@ -1,4 +1,4 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { ProcessPickerComponent } from "../components/process-picker-component";
3
3
  import type { ProcessInfo } from "../constants";
4
4
  import type { ProcessManager } from "../manager";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { DockActions } from "../../hooks/widget";
3
3
  import type { ProcessManager } from "../../manager";
4
4
  import { allProcessCompletions } from "../completions";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { ProcessesComponent } from "../../components/processes-component";
3
3
  import type { DockActions } from "../../hooks/widget";
4
4
  import type { ProcessManager } from "../../manager";
@@ -1,5 +1,5 @@
1
1
  import { registerSettingsCommand } from "@aliou/pi-utils-settings";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { ProcessesConfig, ResolvedProcessesConfig } from "../../config";
4
4
  import { configLoader } from "../../config";
5
5
  import { applySettingChange } from "./apply-setting-change";
@@ -0,0 +1,29 @@
1
+ import { visibleWidth } from "@earendil-works/pi-tui";
2
+ import { describe, expect, it } from "vitest";
3
+ import { renderCollapsedDockLine } from "./log-dock-component";
4
+
5
+ const LONG_LOG_LINE =
6
+ "build-step - 3f7a9c2 pending ↻ https://example.com/pipelines/project/service/123456/workflows/abcdef12-3456-7890-abcd-ef1234567890/jobs/integration-test?token=very-long-unbroken-log-segment-and-query-string";
7
+
8
+ describe("renderCollapsedDockLine", () => {
9
+ it("leaves a spare terminal column for long log lines", () => {
10
+ for (const width of [1, 2, 40, 80, 120]) {
11
+ const rendered = renderCollapsedDockLine(LONG_LOG_LINE, width);
12
+
13
+ expect(visibleWidth(rendered)).toBe(width - 1);
14
+ }
15
+ });
16
+
17
+ it("leaves a spare terminal column for ansi-styled log lines", () => {
18
+ const rendered = renderCollapsedDockLine(
19
+ `\u001b[2m${LONG_LOG_LINE}\u001b[22m`,
20
+ 80,
21
+ );
22
+
23
+ expect(visibleWidth(rendered)).toBe(79);
24
+ });
25
+
26
+ it("renders nothing for zero width", () => {
27
+ expect(renderCollapsedDockLine(LONG_LOG_LINE, 0)).toBe("");
28
+ });
29
+ });
@@ -5,17 +5,18 @@
5
5
  * Open view: LogFileViewer for the focused process (or first running), follow mode on.
6
6
  */
7
7
 
8
+ import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
9
+ import type { Component } from "@earendil-works/pi-tui";
10
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
+ import { LIVE_STATUSES } from "../constants";
12
+ import type { ProcessManager } from "../manager";
13
+ import { stripAnsi } from "../utils";
14
+ import { LogFileViewer } from "./log-file-viewer";
8
15
  import {
9
16
  createPanelPadder,
10
17
  renderPanelRule,
11
18
  renderPanelTitleLine,
12
- } from "@aliou/pi-utils-ui";
13
- import type { Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
14
- import type { Component } from "@mariozechner/pi-tui";
15
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
16
- import { LIVE_STATUSES } from "../constants";
17
- import type { ProcessManager } from "../manager";
18
- import { LogFileViewer } from "./log-file-viewer";
19
+ } from "./panel-helpers";
19
20
 
20
21
  const PROCESS_COLORS: ThemeColor[] = [
21
22
  "accent",
@@ -28,6 +29,24 @@ const PROCESS_COLORS: ThemeColor[] = [
28
29
  "warning",
29
30
  ];
30
31
 
32
+ const COLLAPSED_DOCK_RIGHT_MARGIN = 1;
33
+
34
+ function getCollapsedDockLineWidth(width: number): number {
35
+ return Math.max(0, width - COLLAPSED_DOCK_RIGHT_MARGIN);
36
+ }
37
+
38
+ export function renderCollapsedDockLine(
39
+ content: string,
40
+ width: number,
41
+ ): string {
42
+ const lineWidth = getCollapsedDockLineWidth(width);
43
+ if (lineWidth === 0) return "";
44
+
45
+ const innerWidth = Math.max(0, lineWidth - 2);
46
+ const line = truncateToWidth(content, innerWidth, "", true);
47
+ return ` ${line}${" ".repeat(Math.max(0, lineWidth - 1 - visibleWidth(line)))}`;
48
+ }
49
+
31
50
  interface LogDockOptions {
32
51
  manager: ProcessManager;
33
52
  theme: Theme;
@@ -119,16 +138,12 @@ export class LogDockComponent implements Component {
119
138
  const fg = (color: ThemeColor, s: string) => theme.fg(color, s);
120
139
 
121
140
  const processes = this.manager.list();
122
- const innerWidth = width - 2;
123
- const padLine = (content: string) => {
124
- const w = visibleWidth(content);
125
- const line =
126
- w > innerWidth ? truncateToWidth(content, innerWidth) : content;
127
- return ` ${line}${" ".repeat(Math.max(0, width - 1 - visibleWidth(line)))}`;
128
- };
141
+ const lineWidth = getCollapsedDockLineWidth(width);
142
+ const padLine = (content: string) =>
143
+ renderCollapsedDockLine(content, width);
129
144
 
130
145
  if (processes.length === 0) {
131
- return [renderPanelRule(width, theme), padLine(dim("No processes"))];
146
+ return [renderPanelRule(lineWidth, theme), padLine(dim("No processes"))];
132
147
  }
133
148
 
134
149
  const running = processes.filter((p) => LIVE_STATUSES.has(p.status));
@@ -144,18 +159,12 @@ export class LogDockComponent implements Component {
144
159
  }
145
160
 
146
161
  const firstLine = parts.join(" | ");
147
- const lines = [
148
- renderPanelRule(width, theme),
149
- padLine(truncateToWidth(firstLine, innerWidth)),
150
- ];
162
+ const lines = [renderPanelRule(lineWidth, theme), padLine(firstLine)];
151
163
 
152
164
  if (running.length > 0) {
153
165
  const lastLogs = this.manager.getCombinedOutput(running[0].id, 1);
154
166
  if (lastLogs && lastLogs.length > 0) {
155
- const lastLog = truncateToWidth(
156
- lastLogs[lastLogs.length - 1].text,
157
- innerWidth,
158
- );
167
+ const lastLog = stripAnsi(lastLogs[lastLogs.length - 1].text);
159
168
  lines.push(padLine(dim(lastLog)));
160
169
  }
161
170
  }
@@ -169,12 +178,12 @@ export class LogDockComponent implements Component {
169
178
 
170
179
  const innerWidth = width - 2;
171
180
  const basePadLine = createPanelPadder(width);
172
- const padLine = (content: string): string => {
173
- const w = visibleWidth(content);
174
- return basePadLine(
175
- w > innerWidth ? truncateToWidth(content, innerWidth) : content,
181
+ const padLine = (content: string): string =>
182
+ basePadLine(
183
+ visibleWidth(content) > innerWidth
184
+ ? truncateToWidth(content, innerWidth, "", true)
185
+ : content,
176
186
  );
177
- };
178
187
 
179
188
  const processes = this.manager.list();
180
189
  const running = processes.filter((p) => LIVE_STATUSES.has(p.status));
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  import { readFileSync } from "node:fs";
11
- import type { Theme } from "@mariozechner/pi-coding-agent";
12
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
11
+ import type { Theme } from "@earendil-works/pi-coding-agent";
12
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
13
  import { stripAnsi } from "../utils";
14
14
 
15
15
  export type StreamFilter = "combined" | "stdout" | "stderr";
@@ -14,7 +14,7 @@
14
14
  * ╰───────────────────────────────────────╯
15
15
  */
16
16
 
17
- import type { Theme } from "@mariozechner/pi-coding-agent";
17
+ import type { Theme } from "@earendil-works/pi-coding-agent";
18
18
  import {
19
19
  type Component,
20
20
  Input,
@@ -22,7 +22,7 @@ import {
22
22
  type TUI,
23
23
  truncateToWidth,
24
24
  visibleWidth,
25
- } from "@mariozechner/pi-tui";
25
+ } from "@earendil-works/pi-tui";
26
26
  import { LIVE_STATUSES, type ProcessInfo } from "../constants";
27
27
  import type { ProcessManager } from "../manager";
28
28
  import { LogFileViewer } from "./log-file-viewer";
@@ -0,0 +1,37 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+
4
+ export function createPanelPadder(width: number): (content: string) => string {
5
+ const innerWidth = Math.max(0, width - 2);
6
+
7
+ return (content: string): string => {
8
+ const truncated = truncateToWidth(content, innerWidth, "", true);
9
+ const padding = " ".repeat(
10
+ Math.max(0, innerWidth - visibleWidth(truncated)),
11
+ );
12
+ return `│${truncated}${padding}│`;
13
+ };
14
+ }
15
+
16
+ export function renderPanelRule(width: number, theme: Theme): string {
17
+ if (width <= 0) return "";
18
+ if (width === 1) return theme.fg("dim", "─");
19
+
20
+ return theme.fg("dim", `├${"─".repeat(Math.max(0, width - 2))}┤`);
21
+ }
22
+
23
+ export function renderPanelTitleLine(
24
+ title: string,
25
+ width: number,
26
+ theme: Theme,
27
+ ): string {
28
+ if (width <= 0) return "";
29
+ if (width === 1) return theme.fg("dim", "─");
30
+
31
+ const innerWidth = Math.max(0, width - 2);
32
+ const label = ` ${title} `;
33
+ const truncated = truncateToWidth(label, innerWidth, "", true);
34
+ const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
35
+
36
+ return theme.fg("dim", `┌${truncated}${"─".repeat(remaining)}┐`);
37
+ }
@@ -1,17 +1,17 @@
1
- import {
2
- createPanelPadder,
3
- renderPanelRule,
4
- renderPanelTitleLine,
5
- } from "@aliou/pi-utils-ui";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
7
2
  import {
8
3
  type Component,
9
4
  matchesKey,
10
5
  truncateToWidth,
11
6
  visibleWidth,
12
- } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-tui";
13
8
  import type { ProcessInfo } from "../constants";
14
9
  import type { ProcessManager } from "../manager";
10
+ import {
11
+ createPanelPadder,
12
+ renderPanelRule,
13
+ renderPanelTitleLine,
14
+ } from "./panel-helpers";
15
15
  import { statusIcon, statusLabel } from "./status-format";
16
16
 
17
17
  /**
@@ -121,7 +121,7 @@ export class ProcessPickerComponent implements Component {
121
121
  const padLine = (content: string): string =>
122
122
  basePadLine(
123
123
  visibleWidth(content) > innerWidth
124
- ? truncateToWidth(content, innerWidth)
124
+ ? truncateToWidth(content, innerWidth, "", true)
125
125
  : content,
126
126
  );
127
127
 
@@ -151,7 +151,6 @@ export class ProcessPickerComponent implements Component {
151
151
  lines.push(padLine(""));
152
152
  }
153
153
 
154
- // Footer
155
154
  lines.push(renderPanelRule(width, theme));
156
155
  lines.push(
157
156
  padLine(
@@ -1,19 +1,19 @@
1
- import {
2
- createPanelPadder,
3
- renderPanelRule,
4
- renderPanelTitleLine,
5
- } from "@aliou/pi-utils-ui";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
7
2
  import {
8
3
  type Component,
9
4
  matchesKey,
10
5
  truncateToWidth,
11
6
  visibleWidth,
12
- } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-tui";
13
8
  import { configLoader } from "../config";
14
9
  import type { ProcessInfo } from "../constants";
15
10
  import type { ProcessManager } from "../manager";
16
11
  import { stripAnsi } from "../utils";
12
+ import {
13
+ createPanelPadder,
14
+ renderPanelRule,
15
+ renderPanelTitleLine,
16
+ } from "./panel-helpers";
17
17
  import { statusIcon, statusLabel } from "./status-format";
18
18
 
19
19
  function formatRuntime(startTime: number, endTime: number | null): string {
@@ -236,7 +236,7 @@ export class ProcessesComponent implements Component {
236
236
  const padLine = (content: string): string =>
237
237
  basePadLine(
238
238
  visibleWidth(content) > innerWidth
239
- ? truncateToWidth(content, innerWidth)
239
+ ? truncateToWidth(content, innerWidth, "", true)
240
240
  : content,
241
241
  );
242
242
 
@@ -417,8 +417,6 @@ export class ProcessesComponent implements Component {
417
417
  }
418
418
  }
419
419
 
420
- lines.push(renderPanelRule(width, theme));
421
-
422
420
  const footerLeft =
423
421
  `${dim("enter")} stream ` +
424
422
  `${dim("j/k")} select ` +
@@ -448,6 +446,7 @@ export class ProcessesComponent implements Component {
448
446
  footer = truncateToWidth(footer, innerWidth);
449
447
  }
450
448
 
449
+ lines.push(renderPanelRule(width, theme));
451
450
  lines.push(padLine(footer));
452
451
 
453
452
  this.cachedLines = lines;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { parse } from "@aliou/sh";
9
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
10
  import { walkCommands, wordToString } from "../utils/shell-utils";
11
11
 
12
12
  const BACKGROUND_CMD_NAMES = new Set(["nohup", "disown", "setsid"]);
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessManager } from "../manager";
3
3
 
4
4
  export function setupCleanupHook(pi: ExtensionAPI, manager: ProcessManager) {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { ResolvedProcessesConfig } from "../config";
3
3
  import type { ProcessManager } from "../manager";
4
4
  import { setupBackgroundBlocker } from "./background-blocker";
@@ -2,8 +2,8 @@ import type {
2
2
  ExtensionAPI,
3
3
  MessageRenderOptions,
4
4
  Theme,
5
- } from "@mariozechner/pi-coding-agent";
6
- import { Text } from "@mariozechner/pi-tui";
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { Text } from "@earendil-works/pi-tui";
7
7
  import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
8
8
 
9
9
  interface ProcessLifecycleDetails {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { MESSAGE_TYPE_PROCESS_UPDATE, type ProcessInfo } from "../constants";
3
3
  import type { ProcessManager } from "../manager";
4
4
  import { formatRuntime } from "../utils";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { MESSAGE_TYPE_PROCESS_UPDATE } from "../constants";
3
3
  import type { ProcessManager } from "../manager";
4
4
  import { safeSendMessage } from "./utils";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  /**
4
4
  * Send a message via pi, swallowing stale-context errors.
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
- } from "@mariozechner/pi-coding-agent";
4
+ } from "@earendil-works/pi-coding-agent";
5
5
  import { LogDockComponent } from "../../components/log-dock-component";
6
6
  import { configLoader, type ResolvedProcessesConfig } from "../../config";
7
7
  import { LIVE_STATUSES } from "../../constants";
@@ -1,5 +1,5 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
3
  import type { ProcessInfo } from "../../constants";
4
4
 
5
5
  function formatProcessStatus(
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { setupProcessesCommands } from "./commands";
3
3
  import { registerProcessesSettings } from "./commands/settings";
4
4
  import { configLoader } from "./config";
package/src/manager.ts CHANGED
@@ -750,9 +750,9 @@ export class ProcessManager {
750
750
  }
751
751
 
752
752
  export type {
753
+ KillResult,
754
+ ManagerEvent,
753
755
  ProcessInfo,
754
756
  ProcessStatus,
755
- ManagerEvent,
756
- KillResult,
757
757
  WriteResult,
758
758
  };
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult, ProcessInfo } from "../../constants";
4
4
 
5
5
  interface DebugParams {
@@ -4,8 +4,8 @@ import type {
4
4
  ExtensionContext,
5
5
  Theme,
6
6
  ToolRenderResultOptions,
7
- } from "@mariozechner/pi-coding-agent";
8
- import type { Component } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import type { Component } from "@earendil-works/pi-tui";
9
9
  import type {
10
10
  ExecuteResult,
11
11
  ProcessAction,
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult } from "../../constants";
4
4
  import type { ProcessManager } from "../../manager";
5
5
 
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
9
9
  import type { ProcessManager } from "../../manager";
10
10
  import {
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
9
9
  import type { ProcessManager } from "../../manager";
10
10
 
@@ -3,8 +3,8 @@ import type {
3
3
  AgentToolResult,
4
4
  Theme,
5
5
  ToolRenderResultOptions,
6
- } from "@mariozechner/pi-coding-agent";
7
- import { Text } from "@mariozechner/pi-tui";
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
8
  import { configLoader } from "../../config";
9
9
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
10
  import type { ProcessManager } from "../../manager";
@@ -4,8 +4,8 @@ import type {
4
4
  ExtensionContext,
5
5
  Theme,
6
6
  ToolRenderResultOptions,
7
- } from "@mariozechner/pi-coding-agent";
8
- import { Text } from "@mariozechner/pi-tui";
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import { Text } from "@earendil-works/pi-tui";
9
9
  import type { ExecuteResult, ProcessesDetails } from "../../constants";
10
10
  import type { ProcessManager } from "../../manager";
11
11
 
@@ -1,5 +1,5 @@
1
1
  import { ToolCallHeader } from "@aliou/pi-utils-ui";
2
- import type { Theme } from "@mariozechner/pi-coding-agent";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import type { ExecuteResult } from "../../constants";
4
4
  import type { ProcessManager } from "../../manager";
5
5
 
@@ -1,12 +1,12 @@
1
1
  import { ToolBody } from "@aliou/pi-utils-ui";
2
- import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { StringEnum } from "@earendil-works/pi-ai";
3
3
  import type {
4
4
  AgentToolResult,
5
5
  ExtensionAPI,
6
6
  Theme,
7
7
  ToolRenderResultOptions,
8
- } from "@mariozechner/pi-coding-agent";
9
- import { Text } from "@mariozechner/pi-tui";
8
+ } from "@earendil-works/pi-coding-agent";
9
+ import { Text } from "@earendil-works/pi-tui";
10
10
  import { type Static, Type } from "typebox";
11
11
  import type { ProcessesDetails } from "../constants";
12
12
  import type { ProcessManager } from "../manager";
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { stripAnsi } from "./ansi";
3
+
4
+ describe("stripAnsi", () => {
5
+ it("strips CSI styling sequences", () => {
6
+ expect(stripAnsi("\u001b[31mred\u001b[0m")).toBe("red");
7
+ });
8
+
9
+ it("strips generic OSC sequences", () => {
10
+ expect(stripAnsi("\u001b]0;title\u0007hello")).toBe("hello");
11
+ });
12
+
13
+ it("strips carriage returns and other control chars that can corrupt TUI rendering", () => {
14
+ expect(stripAnsi("step 1\rstep 2\b\b done")).toBe("step 1step 2 done");
15
+ expect(stripAnsi("null\u0000byte")).toBe("nullbyte");
16
+ expect(stripAnsi("delete\u007fchar")).toBe("deletechar");
17
+ });
18
+
19
+ it("preserves tabs and newlines", () => {
20
+ expect(stripAnsi("one\ttwo\nthree")).toBe("one\ttwo\nthree");
21
+ });
22
+ });
package/src/utils/ansi.ts CHANGED
@@ -1,36 +1,45 @@
1
1
  /**
2
- * Strip ANSI escape codes from a string.
2
+ * Strip ANSI escape codes and other terminal control characters from a string.
3
3
  *
4
4
  * Removes:
5
5
  * - All CSI sequences (\x1b[...X) - SGR, cursor movement, erase, scroll, etc.
6
- * - OSC 8 hyperlinks (\x1b]8;;URL\x07)
6
+ * - OSC sequences (\x1b]...\x07 or \x1b]...\x1b\\)
7
7
  * - APC sequences (\x1b_...\x07 or \x1b_...\x1b\\)
8
+ * - Remaining C0 control chars except tab/newline
8
9
  */
10
+ const ESC = String.fromCodePoint(0x001b);
11
+ const BEL = String.fromCodePoint(0x0007);
12
+
13
+ const ANSI_REPLACEMENTS: RegExp[] = [
14
+ // CSI sequences: SGR, cursor movement, erase, scroll, etc.
15
+ new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "gu"),
16
+ // OSC sequences: ESC]...<BEL> or ESC]...<ESC>\\.
17
+ new RegExp(`${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
18
+ // APC sequences: ESC_...<BEL> or ESC_...<ESC>\\.
19
+ new RegExp(`${ESC}_[^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
20
+ ];
21
+
22
+ // Strip C0 terminal control characters that can corrupt TUI layout when
23
+ // rendered back into pi, such as carriage return and backspace. Keep tabs and
24
+ // newlines because logs use them as printable whitespace/line breaks.
25
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: this regex intentionally targets terminal control characters.
26
+ const TERMINAL_CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f]/gu;
27
+
9
28
  /**
10
29
  * Check if a string contains ANSI escape codes.
11
30
  */
12
31
  export function hasAnsi(str: string): boolean {
13
- return str.includes(String.fromCodePoint(0x001b));
32
+ return str.includes(ESC);
14
33
  }
15
34
 
16
35
  export function stripAnsi(str: string): string {
17
- // ESC = \u001b, BEL = \u0007
18
- const ESC = String.fromCodePoint(0x001b);
19
- const BEL = String.fromCodePoint(0x0007);
36
+ let clean = str;
20
37
 
21
- if (!str.includes(ESC)) {
22
- return str;
38
+ if (str.includes(ESC)) {
39
+ for (const pattern of ANSI_REPLACEMENTS) {
40
+ clean = clean.replace(pattern, "");
41
+ }
23
42
  }
24
43
 
25
- // Strip all CSI sequences (ESC[...X where X is any letter)
26
- let clean = str.replace(new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "gu"), "");
27
- // Strip OSC 8 hyperlinks: ESC]8;;URL<BEL> and ESC]8;;<BEL>
28
- clean = clean.replace(new RegExp(`${ESC}\\]8;;[^${BEL}]*${BEL}`, "gu"), "");
29
- // Strip APC sequences: ESC_...<BEL> or ESC_...<ESC>\\ (used for cursor marker)
30
- clean = clean.replace(
31
- new RegExp(`${ESC}_[^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
32
- "",
33
- );
34
-
35
- return clean;
44
+ return clean.replace(TERMINAL_CONTROL_CHARS, "");
36
45
  }
@@ -1,4 +1,4 @@
1
- import type { Theme } from "@mariozechner/pi-coding-agent";
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProcessInfo } from "../constants";
3
3
 
4
4
  export function formatRuntime(