@aliou/pi-processes 0.4.7 → 0.6.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.
Files changed (51) hide show
  1. package/README.md +83 -13
  2. package/package.json +10 -4
  3. package/src/commands/clear/command.ts +14 -0
  4. package/src/commands/clear/index.ts +1 -0
  5. package/src/commands/completions.ts +38 -0
  6. package/src/commands/dock/command.ts +29 -0
  7. package/src/commands/dock/index.ts +1 -0
  8. package/src/commands/index.ts +26 -327
  9. package/src/commands/kill/command.ts +69 -0
  10. package/src/commands/kill/index.ts +1 -0
  11. package/src/commands/logs/command.ts +47 -0
  12. package/src/commands/logs/index.ts +1 -0
  13. package/src/commands/pick-process.ts +34 -0
  14. package/src/commands/pin/command.ts +33 -0
  15. package/src/commands/pin/index.ts +1 -0
  16. package/src/commands/processes/command.ts +39 -0
  17. package/src/commands/processes/index.ts +1 -0
  18. package/src/commands/settings/apply-setting-change.ts +72 -0
  19. package/src/commands/settings/build-sections.ts +160 -0
  20. package/src/commands/settings/command.ts +20 -0
  21. package/src/commands/settings/index.ts +1 -0
  22. package/src/components/log-dock-component.ts +238 -0
  23. package/src/components/log-file-viewer.ts +317 -0
  24. package/src/components/log-overlay-component.ts +555 -0
  25. package/src/components/process-picker-component.ts +14 -2
  26. package/src/components/processes-component.ts +41 -21
  27. package/src/config.ts +38 -1
  28. package/src/constants/index.ts +1 -0
  29. package/src/constants/types.ts +7 -1
  30. package/src/hooks/background-blocker.ts +66 -0
  31. package/src/hooks/index.ts +15 -3
  32. package/src/hooks/process-end.ts +3 -30
  33. package/src/hooks/widget/index.ts +2 -0
  34. package/src/hooks/widget/setup.ts +168 -0
  35. package/src/hooks/{widget.ts → widget/status-widget.ts} +27 -68
  36. package/src/hooks/widget/types.ts +21 -0
  37. package/src/index.ts +9 -3
  38. package/src/manager.ts +61 -9
  39. package/src/tools/actions/index.ts +5 -0
  40. package/src/tools/actions/write.ts +87 -0
  41. package/src/tools/index.ts +82 -14
  42. package/src/utils/command-executor.test.ts +2 -1
  43. package/src/utils/command-executor.ts +1 -1
  44. package/src/utils/keybindings.ts +76 -0
  45. package/src/utils/shell-utils.ts +133 -0
  46. package/src/commands/settings-command.ts +0 -157
  47. package/src/components/log-stream-component.ts +0 -149
  48. package/src/test/test-exit-crash.sh +0 -19
  49. package/src/test/test-exit-failure.sh +0 -17
  50. package/src/test/test-exit-success.sh +0 -16
  51. package/src/test/test-output.sh +0 -28
@@ -1,157 +0,0 @@
1
- import {
2
- registerSettingsCommand,
3
- type SettingsSection,
4
- } from "@aliou/pi-utils-settings";
5
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
6
- import type { ProcessesConfig, ResolvedProcessesConfig } from "../config";
7
- import { configLoader } from "../config";
8
-
9
- export function registerProcessesSettings(
10
- pi: ExtensionAPI,
11
- onSave?: () => void,
12
- ): void {
13
- registerSettingsCommand<ProcessesConfig, ResolvedProcessesConfig>(pi, {
14
- commandName: "process:settings",
15
- title: "Processes Settings",
16
- configStore: configLoader,
17
- buildSections: (
18
- tabConfig: ProcessesConfig | null,
19
- resolved: ResolvedProcessesConfig,
20
- ): SettingsSection[] => {
21
- return [
22
- {
23
- label: "Process List",
24
- items: [
25
- {
26
- id: "processList.maxVisibleProcesses",
27
- label: "Max visible processes",
28
- description:
29
- "Maximum processes shown in the /processes list before scrolling",
30
- currentValue: String(
31
- tabConfig?.processList?.maxVisibleProcesses ??
32
- resolved.processList.maxVisibleProcesses,
33
- ),
34
- values: ["4", "6", "8", "12", "16"],
35
- },
36
- {
37
- id: "processList.maxPreviewLines",
38
- label: "Max preview lines",
39
- description: "Log preview lines shown below the selected process",
40
- currentValue: String(
41
- tabConfig?.processList?.maxPreviewLines ??
42
- resolved.processList.maxPreviewLines,
43
- ),
44
- values: ["6", "8", "12", "16", "24"],
45
- },
46
- ],
47
- },
48
- {
49
- label: "Output Limits",
50
- items: [
51
- {
52
- id: "output.defaultTailLines",
53
- label: "Default tail lines",
54
- description:
55
- "Number of tail lines returned to the agent by default",
56
- currentValue: String(
57
- tabConfig?.output?.defaultTailLines ??
58
- resolved.output.defaultTailLines,
59
- ),
60
- values: ["50", "100", "200", "500"],
61
- },
62
- {
63
- id: "output.maxOutputLines",
64
- label: "Max output lines",
65
- description: "Hard cap on output lines returned to the agent",
66
- currentValue: String(
67
- tabConfig?.output?.maxOutputLines ??
68
- resolved.output.maxOutputLines,
69
- ),
70
- values: ["100", "200", "500", "1000"],
71
- },
72
- ],
73
- },
74
- {
75
- label: "Execution",
76
- items: [
77
- {
78
- id: "execution.shellPath",
79
- label: "Shell path",
80
- description:
81
- "Absolute shell path override used to execute commands",
82
- currentValue:
83
- tabConfig?.execution?.shellPath ??
84
- resolved.execution.shellPath ??
85
- "auto",
86
- values: [
87
- "auto",
88
- "/run/current-system/sw/bin/bash",
89
- "/bin/bash",
90
- "/usr/bin/bash",
91
- "/usr/local/bin/bash",
92
- ],
93
- },
94
- ],
95
- },
96
- {
97
- label: "Widget",
98
- items: [
99
- {
100
- id: "widget.showStatusWidget",
101
- label: "Show status widget",
102
- description: "Show process status widget below the editor",
103
- currentValue:
104
- (tabConfig?.widget?.showStatusWidget ??
105
- resolved.widget.showStatusWidget)
106
- ? "on"
107
- : "off",
108
- values: ["on", "off"],
109
- },
110
- ],
111
- },
112
- ];
113
- },
114
- onSettingChange: (id, newValue, config) => {
115
- const updated = structuredClone(config);
116
- // Boolean fields.
117
- if (id === "widget.showStatusWidget") {
118
- if (!updated.widget) updated.widget = {};
119
- updated.widget.showStatusWidget = newValue === "on";
120
- return updated;
121
- }
122
- if (id === "execution.shellPath") {
123
- if (!updated.execution) updated.execution = {};
124
- updated.execution.shellPath =
125
- newValue === "auto" ? undefined : newValue;
126
- return updated;
127
- }
128
-
129
- // Numeric fields.
130
- const num = Number.parseInt(newValue, 10);
131
- if (Number.isNaN(num)) return null;
132
-
133
- switch (id) {
134
- case "processList.maxVisibleProcesses":
135
- if (!updated.processList) updated.processList = {};
136
- updated.processList.maxVisibleProcesses = num;
137
- break;
138
- case "processList.maxPreviewLines":
139
- if (!updated.processList) updated.processList = {};
140
- updated.processList.maxPreviewLines = num;
141
- break;
142
- case "output.defaultTailLines":
143
- if (!updated.output) updated.output = {};
144
- updated.output.defaultTailLines = num;
145
- break;
146
- case "output.maxOutputLines":
147
- if (!updated.output) updated.output = {};
148
- updated.output.maxOutputLines = num;
149
- break;
150
- default:
151
- return null;
152
- }
153
- return updated;
154
- },
155
- onSave,
156
- });
157
- }
@@ -1,149 +0,0 @@
1
- import {
2
- createPanelPadder,
3
- renderPanelRule,
4
- renderPanelTitleLine,
5
- } from "@aliou/pi-utils-ui";
6
- import type { Theme } from "@mariozechner/pi-coding-agent";
7
- import {
8
- type Component,
9
- truncateToWidth,
10
- visibleWidth,
11
- } from "@mariozechner/pi-tui";
12
- import type { ProcessManager } from "../manager";
13
- import { stripAnsi } from "../utils";
14
- import { statusIcon, statusLabel } from "./status-format";
15
-
16
- const MAX_LOG_LINES = 16;
17
- const POLL_INTERVAL_MS = 500;
18
-
19
- export class LogStreamComponent implements Component {
20
- private tui: { requestRender: () => void };
21
- private theme: Theme;
22
- private manager: ProcessManager;
23
- private processId: string;
24
- private timer: ReturnType<typeof setInterval> | null = null;
25
- private unsubscribe: (() => void) | null = null;
26
- private cachedLines: string[] = [];
27
- private cachedWidth = 0;
28
-
29
- constructor(
30
- tui: { requestRender: () => void },
31
- theme: Theme,
32
- manager: ProcessManager,
33
- processId: string,
34
- ) {
35
- this.tui = tui;
36
- this.theme = theme;
37
- this.manager = manager;
38
- this.processId = processId;
39
-
40
- // Poll log file for new output.
41
- this.timer = setInterval(() => {
42
- this.invalidate();
43
- this.tui.requestRender();
44
- }, POLL_INTERVAL_MS);
45
-
46
- // Also re-render on process events (status changes, etc.).
47
- this.unsubscribe = this.manager.onEvent(() => {
48
- this.invalidate();
49
- this.tui.requestRender();
50
- });
51
- }
52
-
53
- handleInput(_data: string): boolean {
54
- // Widget doesn't handle input — the editor is still active.
55
- return false;
56
- }
57
-
58
- invalidate(): void {
59
- this.cachedWidth = 0;
60
- this.cachedLines = [];
61
- }
62
-
63
- render(width: number): string[] {
64
- if (width === this.cachedWidth && this.cachedLines.length > 0) {
65
- return this.cachedLines;
66
- }
67
-
68
- const theme = this.theme;
69
- const dim = (s: string) => theme.fg("dim", s);
70
- const warning = (s: string) => theme.fg("warning", s);
71
- const innerWidth = width - 2;
72
-
73
- const basePadLine = createPanelPadder(width);
74
- const padLine = (content: string): string => {
75
- const contentWidth = visibleWidth(content);
76
- return basePadLine(
77
- contentWidth > innerWidth
78
- ? truncateToWidth(content, innerWidth)
79
- : content,
80
- );
81
- };
82
-
83
- const lines: string[] = [];
84
- const proc = this.manager.get(this.processId);
85
-
86
- if (!proc) {
87
- lines.push(renderPanelRule(width, theme));
88
- lines.push(padLine(warning("Process not found")));
89
- lines.push(renderPanelRule(width, theme));
90
- this.cachedLines = lines;
91
- this.cachedWidth = width;
92
- return this.cachedLines;
93
- }
94
-
95
- // Header
96
- const icon = statusIcon(proc.status, proc.success);
97
- const label = statusLabel(proc);
98
- lines.push(
99
- renderPanelTitleLine(
100
- `Process: ${proc.name} (${proc.id}) ${icon} ${label}`,
101
- width,
102
- theme,
103
- ),
104
- );
105
-
106
- // Log lines (interleaved stdout + stderr in temporal order).
107
- const logLines = this.manager.getCombinedOutput(
108
- this.processId,
109
- MAX_LOG_LINES,
110
- );
111
- if (logLines && logLines.length > 0) {
112
- for (const line of logLines) {
113
- const cleaned = stripAnsi(line.text);
114
- const display = truncateToWidth(cleaned, innerWidth - 2);
115
- if (line.type === "stderr") {
116
- lines.push(padLine(warning(display)));
117
- } else {
118
- lines.push(padLine(display));
119
- }
120
- }
121
- } else {
122
- lines.push(padLine(dim("(no output yet)")));
123
- }
124
-
125
- // Pad to MAX_LOG_LINES for stable height.
126
- const renderedLogLines = lines.length - 1; // minus header
127
- for (let i = renderedLogLines; i < MAX_LOG_LINES; i++) {
128
- lines.push(padLine(""));
129
- }
130
-
131
- // Footer hint
132
- lines.push(renderPanelRule(width, theme));
133
- lines.push(padLine(dim("Run /process:stream to dismiss")));
134
- lines.push(renderPanelRule(width, theme));
135
-
136
- this.cachedLines = lines;
137
- this.cachedWidth = width;
138
- return this.cachedLines;
139
- }
140
-
141
- dispose(): void {
142
- if (this.timer) {
143
- clearInterval(this.timer);
144
- this.timer = null;
145
- }
146
- this.unsubscribe?.();
147
- this.unsubscribe = null;
148
- }
149
- }
@@ -1,19 +0,0 @@
1
- #!/bin/bash
2
- # Test script that simulates a crash (exit code 137 - like SIGKILL)
3
- # Usage: ./test-exit-crash.sh [seconds]
4
-
5
- WAIT_SECONDS=${1:-17}
6
-
7
- echo "Starting unstable task..."
8
- echo "Will crash in ${WAIT_SECONDS} seconds"
9
-
10
- for i in $(seq 1 $WAIT_SECONDS); do
11
- echo "[$(date '+%H:%M:%S')] Running... ($i/$WAIT_SECONDS)"
12
- if [ $i -eq $((WAIT_SECONDS - 1)) ]; then
13
- echo "[WARN] Memory pressure detected" >&2
14
- fi
15
- sleep 1
16
- done
17
-
18
- echo "FATAL: Segmentation fault (core dumped)" >&2
19
- exit 137
@@ -1,17 +0,0 @@
1
- #!/bin/bash
2
- # Test script that exits with failure (exit code 1)
3
- # Usage: ./test-exit-failure.sh [seconds]
4
-
5
- WAIT_SECONDS=${1:-15}
6
-
7
- echo "Starting failing task..."
8
- echo "Will fail in ${WAIT_SECONDS} seconds"
9
-
10
- for i in $(seq 1 $WAIT_SECONDS); do
11
- echo "[$(date '+%H:%M:%S')] Processing... ($i/$WAIT_SECONDS)"
12
- sleep 1
13
- done
14
-
15
- echo "ERROR: Task failed!" >&2
16
- echo "Something went wrong!" >&2
17
- exit 1
@@ -1,16 +0,0 @@
1
- #!/bin/bash
2
- # Test script that exits successfully (exit code 0)
3
- # Usage: ./test-exit-success.sh [seconds]
4
-
5
- WAIT_SECONDS=${1:-13}
6
-
7
- echo "Starting successful task..."
8
- echo "Will complete in ${WAIT_SECONDS} seconds"
9
-
10
- for i in $(seq 1 $WAIT_SECONDS); do
11
- echo "[$(date '+%H:%M:%S')] Working... ($i/$WAIT_SECONDS)"
12
- sleep 1
13
- done
14
-
15
- echo "Task completed successfully!"
16
- exit 0
@@ -1,28 +0,0 @@
1
- #!/bin/bash
2
- # Test script for processes extension
3
- # Writes 80 characters every second, empty line every 10 seconds
4
-
5
- counter=0
6
- while true; do
7
- counter=$((counter + 1))
8
-
9
- # Generate 80 characters: timestamp + padding
10
- timestamp=$(date '+%H:%M:%S')
11
- line=$(printf "[%s] Line %05d: " "$timestamp" "$counter")
12
- # Pad to 80 chars with random chars
13
- padding_len=$((80 - ${#line}))
14
- padding=$(head -c $padding_len /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' 2>/dev/null || printf '%*s' "$padding_len" '' | tr ' ' 'x')
15
- echo "${line}${padding}"
16
-
17
- # Every 10 seconds, print an empty line
18
- if [ $((counter % 10)) -eq 0 ]; then
19
- echo ""
20
- fi
21
-
22
- # Every 5 lines, write something to stderr
23
- if [ $((counter % 5)) -eq 0 ]; then
24
- echo "[WARN] Counter reached $counter" >&2
25
- fi
26
-
27
- sleep 1
28
- done