@aliou/pi-processes 0.9.4 → 0.10.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/CONTRIBUTING.md +40 -42
- package/README.md +54 -84
- package/extensions/process-tabs.ts +26 -0
- package/extensions/processes/client.ts +194 -0
- package/extensions/processes/commands/clear.ts +29 -0
- package/extensions/processes/commands/kill.ts +152 -0
- package/extensions/processes/commands/overview.ts +102 -0
- package/extensions/processes/components/overview-component.ts +729 -0
- package/extensions/processes/components/overview-panel.ts +177 -0
- package/extensions/processes/config/defaults.ts +28 -0
- package/extensions/processes/config/index.ts +13 -0
- package/extensions/processes/config/loader.ts +62 -0
- package/extensions/processes/config/migrations/000-import-legacy-process-config.ts +113 -0
- package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +184 -0
- package/extensions/processes/config/migrations/002-stamp-v0-10-0-config-version.ts +21 -0
- package/extensions/processes/config/migrations/index.ts +25 -0
- package/extensions/processes/config/schema.ts +9 -0
- package/extensions/processes/config/types.ts +72 -0
- package/extensions/processes/constants.ts +1 -0
- package/extensions/processes/handlers/commands.ts +113 -0
- package/extensions/processes/handlers/kill-process.ts +31 -0
- package/extensions/processes/handlers/notifications.ts +147 -0
- package/extensions/processes/handlers/requests.ts +114 -0
- package/extensions/processes/handlers/subscriptions.ts +144 -0
- package/extensions/processes/hooks/background-blocker.ts +201 -0
- package/extensions/processes/hooks/cleanup.ts +43 -0
- package/extensions/processes/hooks/event-bridge.ts +32 -0
- package/extensions/processes/i18n/index.ts +3 -0
- package/extensions/processes/i18n/messages.ts +27 -0
- package/extensions/processes/i18n/translator.ts +49 -0
- package/extensions/processes/index.ts +109 -0
- package/extensions/processes/message-renderer.ts +117 -0
- package/extensions/processes/notification-sender.ts +43 -0
- package/extensions/processes/notifications/classify.ts +27 -0
- package/extensions/processes/notifications/log-matchers.ts +146 -0
- package/extensions/processes/notifications/registry.ts +225 -0
- package/extensions/processes/notifications/render-content.ts +122 -0
- package/extensions/processes/notifications/service.ts +336 -0
- package/extensions/processes/notifications/types.ts +11 -0
- package/extensions/processes/settings/apply-setting-change.ts +30 -0
- package/extensions/processes/settings/build-sections.ts +420 -0
- package/extensions/processes/settings/index.ts +42 -0
- package/extensions/processes/tools/clear/index.ts +21 -0
- package/extensions/processes/tools/clear/render.ts +40 -0
- package/extensions/processes/tools/components/index.ts +4 -0
- package/extensions/processes/tools/components/process-action-header.ts +31 -0
- package/extensions/processes/tools/components/process-action-title.ts +17 -0
- package/extensions/processes/tools/components/tool-layout.ts +70 -0
- package/extensions/processes/tools/components/watch.ts +53 -0
- package/extensions/processes/tools/index.ts +265 -0
- package/extensions/processes/tools/list/index.ts +157 -0
- package/extensions/processes/tools/list/render.ts +211 -0
- package/extensions/processes/tools/notify.ts +191 -0
- package/extensions/processes/tools/output/index.ts +307 -0
- package/extensions/processes/tools/output/render.ts +229 -0
- package/extensions/processes/tools/schema.ts +263 -0
- package/extensions/processes/tools/start/index.ts +64 -0
- package/extensions/processes/tools/start/render.ts +67 -0
- package/extensions/processes/tools/stop/index.ts +35 -0
- package/extensions/processes/tools/stop/render.ts +43 -0
- package/extensions/processes/tools/update/index.ts +320 -0
- package/extensions/processes/tools/update/render.ts +251 -0
- package/extensions/processes/tools/utils.ts +112 -0
- package/extensions/processes/tools/watch-format.ts +14 -0
- package/extensions/processes/tools/write/index.ts +75 -0
- package/extensions/processes/tools/write/render.ts +68 -0
- package/extensions/processes/utils/truncate.ts +285 -0
- package/extensions/processes-dock/client.ts +60 -0
- package/extensions/processes-dock/commands/dock.ts +70 -0
- package/extensions/processes-dock/commands/pin.ts +166 -0
- package/extensions/processes-dock/components/log-dock-component.ts +375 -0
- package/extensions/processes-dock/dock-state.ts +33 -0
- package/extensions/processes-dock/index.ts +28 -0
- package/extensions/processes-dock/logs-client.ts +5 -0
- package/extensions/processes-dock/widget/setup.ts +520 -0
- package/extensions/processes-dock/widget/status.ts +149 -0
- package/extensions/processes-dock/widget/types.ts +16 -0
- package/extensions/processes-logs/client.ts +51 -0
- package/extensions/processes-logs/commands/logs.ts +108 -0
- package/extensions/processes-logs/completions.ts +31 -0
- package/extensions/processes-logs/components/log-file-viewer.ts +274 -0
- package/extensions/processes-logs/components/log-overlay-component.ts +685 -0
- package/extensions/processes-logs/index.ts +27 -0
- package/extensions/processes-logs/logs-client.ts +101 -0
- package/extensions/shared/line-buffer.ts +35 -0
- package/extensions/shared/output-payload.ts +28 -0
- package/extensions/shared/ui.ts +146 -0
- package/package.json +47 -20
- package/schema.json +134 -0
- package/skills/pi-processes/SKILL.md +496 -25
- package/src/get-manager.ts +15 -0
- package/src/manager/index.ts +182 -0
- package/src/manager/internal-types.ts +130 -0
- package/src/manager/limits.ts +15 -0
- package/src/manager/process-log-store.ts +422 -0
- package/src/manager/process-output.ts +345 -0
- package/src/manager/process-registry.ts +78 -0
- package/src/manager/process-runtime-controller.ts +499 -0
- package/src/protocol/broadcasts.ts +14 -0
- package/src/protocol/channels.ts +30 -0
- package/src/protocol/commands.ts +24 -0
- package/src/protocol/index.ts +34 -0
- package/src/protocol/logs.ts +23 -0
- package/src/protocol/notifications.ts +48 -0
- package/src/protocol/requests.ts +76 -0
- package/src/types.ts +66 -0
- package/src/utils/ansi.ts +2 -4
- package/src/utils/format.ts +24 -32
- package/src/utils/index.ts +5 -1
- package/src/utils/is-record.ts +3 -0
- package/src/utils/match-line.ts +32 -0
- package/src/utils/platform.ts +13 -0
- package/src/utils/shorten-path.ts +11 -0
- package/src/utils/signals.ts +22 -0
- package/src/commands/clear/command.ts +0 -14
- package/src/commands/clear/index.ts +0 -1
- package/src/commands/completions.ts +0 -38
- package/src/commands/dock/command.ts +0 -29
- package/src/commands/dock/index.ts +0 -1
- package/src/commands/index.ts +0 -33
- package/src/commands/kill/command.ts +0 -69
- package/src/commands/kill/index.ts +0 -1
- package/src/commands/logs/command.ts +0 -47
- package/src/commands/logs/index.ts +0 -1
- package/src/commands/pick-process.ts +0 -34
- package/src/commands/pin/command.ts +0 -34
- package/src/commands/pin/index.ts +0 -1
- package/src/commands/processes/command.ts +0 -39
- package/src/commands/processes/index.ts +0 -1
- package/src/commands/settings/apply-setting-change.ts +0 -72
- package/src/commands/settings/build-sections.ts +0 -160
- package/src/commands/settings/command.ts +0 -20
- package/src/commands/settings/index.ts +0 -1
- package/src/components/log-dock-component.test.ts +0 -29
- package/src/components/log-dock-component.ts +0 -236
- package/src/components/log-file-viewer.ts +0 -317
- package/src/components/log-overlay-component.ts +0 -546
- package/src/components/panel-helpers.ts +0 -37
- package/src/components/process-picker-component.ts +0 -166
- package/src/components/processes-component.ts +0 -485
- package/src/components/status-format.ts +0 -38
- package/src/config.ts +0 -107
- package/src/constants/index.ts +0 -16
- package/src/constants/types.ts +0 -106
- package/src/hooks/background-blocker.ts +0 -66
- package/src/hooks/cleanup.ts +0 -10
- package/src/hooks/index.ts +0 -32
- package/src/hooks/message-renderer.ts +0 -115
- package/src/hooks/process-end.ts +0 -69
- package/src/hooks/process-watch.ts +0 -85
- package/src/hooks/utils.ts +0 -23
- package/src/hooks/widget/index.ts +0 -2
- package/src/hooks/widget/setup.ts +0 -167
- package/src/hooks/widget/status-widget.ts +0 -105
- package/src/hooks/widget/types.ts +0 -20
- package/src/index.ts +0 -35
- package/src/manager.test.ts +0 -331
- package/src/manager.ts +0 -758
- package/src/tools/actions/clear.ts +0 -20
- package/src/tools/actions/debug.ts +0 -148
- package/src/tools/actions/index.ts +0 -171
- package/src/tools/actions/kill.ts +0 -89
- package/src/tools/actions/list.ts +0 -188
- package/src/tools/actions/logs.ts +0 -117
- package/src/tools/actions/output.ts +0 -269
- package/src/tools/actions/start.ts +0 -245
- package/src/tools/actions/write.ts +0 -113
- package/src/tools/index.ts +0 -207
- package/src/utils/ansi.test.ts +0 -22
- package/src/utils/command-executor.test.ts +0 -48
- package/src/utils/keybindings.ts +0 -71
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ProcessInfo } from "../types";
|
|
2
|
+
|
|
3
|
+
// UI emits, core listens and calls reply synchronously.
|
|
4
|
+
// Reply callbacks make this an in-process protocol, not serializable IPC/RPC.
|
|
5
|
+
|
|
6
|
+
export interface RequestListPayload {
|
|
7
|
+
reply: (processes: ProcessInfo[]) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RequestGetPayload {
|
|
11
|
+
id: string;
|
|
12
|
+
reply: (info: ProcessInfo | null) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RequestOutputPayload {
|
|
16
|
+
id: string;
|
|
17
|
+
tailLines?: number;
|
|
18
|
+
reply: (
|
|
19
|
+
output: { stdout: string[]; stderr: string[]; status: string } | null,
|
|
20
|
+
) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RequestCombinedOutputPayload {
|
|
24
|
+
id: string;
|
|
25
|
+
tailLines?: number;
|
|
26
|
+
reply: (
|
|
27
|
+
lines: Array<{ type: "stdout" | "stderr"; text: string }> | null,
|
|
28
|
+
) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RequestLogFilesPayload {
|
|
32
|
+
id: string;
|
|
33
|
+
reply: (
|
|
34
|
+
files: {
|
|
35
|
+
stdoutFile: string;
|
|
36
|
+
stderrFile: string;
|
|
37
|
+
combinedFile: string;
|
|
38
|
+
} | null,
|
|
39
|
+
) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RequestFileSizePayload {
|
|
43
|
+
id: string;
|
|
44
|
+
reply: (sizes: { stdout: number; stderr: number } | null) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProcessProtocolConfig {
|
|
48
|
+
execution: {
|
|
49
|
+
shellPath: string | undefined;
|
|
50
|
+
};
|
|
51
|
+
interception: {
|
|
52
|
+
blockBackgroundCommands: boolean;
|
|
53
|
+
};
|
|
54
|
+
processList: {
|
|
55
|
+
maxPreviewLines: number;
|
|
56
|
+
maxVisibleProcesses: number;
|
|
57
|
+
};
|
|
58
|
+
output: {
|
|
59
|
+
defaultTailLines: number;
|
|
60
|
+
maxOutputLines: number;
|
|
61
|
+
maxOutputBytes: number;
|
|
62
|
+
};
|
|
63
|
+
follow: {
|
|
64
|
+
enabledByDefault: boolean;
|
|
65
|
+
autoHideOnFinish: boolean;
|
|
66
|
+
};
|
|
67
|
+
widget: {
|
|
68
|
+
showStatusWidget: boolean;
|
|
69
|
+
dockDefaultState: "closed" | "collapsed" | "expanded";
|
|
70
|
+
dockHeight: number;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RequestConfigPayload {
|
|
75
|
+
reply: (config: ProcessProtocolConfig) => void;
|
|
76
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ProcessStatus =
|
|
2
|
+
| "running"
|
|
3
|
+
| "terminating"
|
|
4
|
+
| "terminate_timeout"
|
|
5
|
+
| "exited"
|
|
6
|
+
| "killed";
|
|
7
|
+
|
|
8
|
+
export const LIVE_STATUSES: ReadonlySet<ProcessStatus> = new Set([
|
|
9
|
+
"running",
|
|
10
|
+
"terminating",
|
|
11
|
+
"terminate_timeout",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export type ProcessEndReason =
|
|
15
|
+
| "exit"
|
|
16
|
+
| "signal"
|
|
17
|
+
| "spawn_error"
|
|
18
|
+
| "missing_pid"
|
|
19
|
+
| "kill_timeout"
|
|
20
|
+
| "lost";
|
|
21
|
+
|
|
22
|
+
export interface ProcessSignalInfo {
|
|
23
|
+
name: NodeJS.Signals;
|
|
24
|
+
number: number | null;
|
|
25
|
+
description: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ProcessInfo {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
pid: number; // On Unix, this is also the PGID (process group leader)
|
|
32
|
+
command: string;
|
|
33
|
+
cwd: string;
|
|
34
|
+
startTime: number;
|
|
35
|
+
endTime: number | null;
|
|
36
|
+
status: ProcessStatus;
|
|
37
|
+
exitCode: number | null;
|
|
38
|
+
success: boolean | null; // null if running, true if exit code 0, false otherwise
|
|
39
|
+
stdoutFile: string;
|
|
40
|
+
stderrFile: string;
|
|
41
|
+
endReason: ProcessEndReason | null;
|
|
42
|
+
signal: ProcessSignalInfo | null;
|
|
43
|
+
errorMessage: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ManagerEvent =
|
|
47
|
+
| { type: "process_started"; info: ProcessInfo }
|
|
48
|
+
| { type: "process_ended"; info: ProcessInfo }
|
|
49
|
+
| {
|
|
50
|
+
type: "process_output_changed";
|
|
51
|
+
id: string;
|
|
52
|
+
appendedText?: Array<{ type: "stdout" | "stderr"; text: string }>;
|
|
53
|
+
droppedLines?: number;
|
|
54
|
+
}
|
|
55
|
+
| { type: "processes_changed" };
|
|
56
|
+
|
|
57
|
+
export type KillResult =
|
|
58
|
+
| { ok: true; info: ProcessInfo }
|
|
59
|
+
| { ok: false; info: ProcessInfo; reason: "not_found" | "timeout" | "error" };
|
|
60
|
+
|
|
61
|
+
export type WriteResult =
|
|
62
|
+
| { ok: true }
|
|
63
|
+
| {
|
|
64
|
+
ok: false;
|
|
65
|
+
reason: "not_found" | "process_exited" | "stdin_closed" | "write_error";
|
|
66
|
+
};
|
package/src/utils/ansi.ts
CHANGED
|
@@ -12,7 +12,7 @@ const BEL = String.fromCodePoint(0x0007);
|
|
|
12
12
|
|
|
13
13
|
const ANSI_REPLACEMENTS: RegExp[] = [
|
|
14
14
|
// CSI sequences: SGR, cursor movement, erase, scroll, etc.
|
|
15
|
-
new RegExp(`${ESC}\\[[
|
|
15
|
+
new RegExp(`${ESC}\\[[\\x30-\\x3f]*[\\x20-\\x2f]*[\\x40-\\x7e]`, "gu"),
|
|
16
16
|
// OSC sequences: ESC]...<BEL> or ESC]...<ESC>\\.
|
|
17
17
|
new RegExp(`${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "gu"),
|
|
18
18
|
// APC sequences: ESC_...<BEL> or ESC_...<ESC>\\.
|
|
@@ -25,9 +25,7 @@ const ANSI_REPLACEMENTS: RegExp[] = [
|
|
|
25
25
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: this regex intentionally targets terminal control characters.
|
|
26
26
|
const TERMINAL_CONTROL_CHARS = /[\u0000-\u0008\u000b-\u001f\u007f]/gu;
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Check if a string contains ANSI escape codes.
|
|
30
|
-
*/
|
|
28
|
+
/** Check if a string contains ANSI escape codes. */
|
|
31
29
|
export function hasAnsi(str: string): boolean {
|
|
32
30
|
return str.includes(ESC);
|
|
33
31
|
}
|
package/src/utils/format.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ProcessInfo } from "../constants";
|
|
1
|
+
import type { ProcessStatus } from "../types";
|
|
3
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Format a process runtime as a human-readable string.
|
|
5
|
+
* "3s", "2m 15s", "1h 30m"
|
|
6
|
+
*/
|
|
4
7
|
export function formatRuntime(
|
|
5
8
|
startTime: number,
|
|
6
9
|
endTime: number | null,
|
|
10
|
+
now: number = Date.now(),
|
|
7
11
|
): string {
|
|
8
|
-
const end = endTime ??
|
|
9
|
-
const ms = end - startTime;
|
|
12
|
+
const end = endTime ?? now;
|
|
13
|
+
const ms = Math.max(0, end - startTime);
|
|
10
14
|
const seconds = Math.floor(ms / 1000);
|
|
11
15
|
const minutes = Math.floor(seconds / 60);
|
|
12
16
|
const hours = Math.floor(minutes / 60);
|
|
@@ -20,8 +24,16 @@ export function formatRuntime(
|
|
|
20
24
|
return `${seconds}s`;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Format a process status as a plain string.
|
|
29
|
+
* "running", "exit(0)", "exit(1)"
|
|
30
|
+
*/
|
|
31
|
+
export function formatStatus(proc: {
|
|
32
|
+
status: string;
|
|
33
|
+
success: boolean | null;
|
|
34
|
+
exitCode: number | null;
|
|
35
|
+
}): string {
|
|
36
|
+
switch (proc.status as ProcessStatus) {
|
|
25
37
|
case "running":
|
|
26
38
|
return "running";
|
|
27
39
|
case "terminating":
|
|
@@ -37,38 +49,18 @@ export function formatStatus(proc: ProcessInfo): string {
|
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Truncate a command string to a maximum length.
|
|
54
|
+
*/
|
|
40
55
|
export function truncateCmd(cmd: string, max = 40): string {
|
|
41
56
|
if (cmd.length <= max) return cmd;
|
|
42
57
|
return `${cmd.slice(0, max - 3)}...`;
|
|
43
58
|
}
|
|
44
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Format a timestamp as an ISO string or "-" if null.
|
|
62
|
+
*/
|
|
45
63
|
export function formatTimestamp(ts: number | null): string {
|
|
46
64
|
if (!ts) return "-";
|
|
47
65
|
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
|
48
66
|
}
|
|
49
|
-
|
|
50
|
-
export function formatStatusTag(
|
|
51
|
-
process: {
|
|
52
|
-
status: string;
|
|
53
|
-
success: boolean | null;
|
|
54
|
-
exitCode: number | null;
|
|
55
|
-
},
|
|
56
|
-
theme: Theme,
|
|
57
|
-
): string {
|
|
58
|
-
switch (process.status) {
|
|
59
|
-
case "running":
|
|
60
|
-
return theme.fg("accent", "running");
|
|
61
|
-
case "terminating":
|
|
62
|
-
return theme.fg("warning", "terminating");
|
|
63
|
-
case "terminate_timeout":
|
|
64
|
-
return theme.fg("error", "terminate_timeout");
|
|
65
|
-
case "killed":
|
|
66
|
-
return theme.fg("warning", "killed");
|
|
67
|
-
case "exited":
|
|
68
|
-
return process.success
|
|
69
|
-
? theme.fg("success", "exit(0)")
|
|
70
|
-
: theme.fg("error", `exit(${process.exitCode ?? "?"})`);
|
|
71
|
-
default:
|
|
72
|
-
return theme.fg("muted", process.status);
|
|
73
|
-
}
|
|
74
|
-
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { hasAnsi, stripAnsi } from "./ansi";
|
|
2
|
+
export { resolveShellExecutable, spawnCommand } from "./command-executor";
|
|
2
3
|
export {
|
|
3
4
|
formatRuntime,
|
|
4
5
|
formatStatus,
|
|
5
|
-
formatStatusTag,
|
|
6
6
|
formatTimestamp,
|
|
7
7
|
truncateCmd,
|
|
8
8
|
} from "./format";
|
|
9
|
+
export type { LineMatchMode } from "./match-line";
|
|
10
|
+
export { compileLineMatcher } from "./match-line";
|
|
9
11
|
export { isProcessGroupAlive, killProcessGroup } from "./process-group";
|
|
12
|
+
export { walkCommands, wordToString } from "./shell-utils";
|
|
13
|
+
export { shortenPath } from "./shorten-path";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared line-matching logic used by both the notification log-matchers
|
|
3
|
+
* and the output tool action. Pi-agnostic — no Pi imports.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type LineMatchMode = "literal" | "regex";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compile a pattern + mode into a line-matching predicate.
|
|
10
|
+
*
|
|
11
|
+
* - literal: substring match (case-sensitive).
|
|
12
|
+
* - regex: RegExp test (throws on invalid pattern).
|
|
13
|
+
*/
|
|
14
|
+
export function compileLineMatcher(
|
|
15
|
+
pattern: string,
|
|
16
|
+
mode: LineMatchMode,
|
|
17
|
+
): (line: string) => boolean {
|
|
18
|
+
// Guard against the match-all footgun for an empty pattern. An empty
|
|
19
|
+
// literal would match every line via String#includes(""), and an empty
|
|
20
|
+
// regex matches at every position. Callers should treat "" as "no filter",
|
|
21
|
+
// but defend here too so the shared primitive can never become a match-all.
|
|
22
|
+
if (pattern.length === 0) {
|
|
23
|
+
return () => false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (mode === "literal") {
|
|
27
|
+
return (line) => line.includes(pattern);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const regex = new RegExp(pattern);
|
|
31
|
+
return (line) => regex.test(line);
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform gate for the processes extensions.
|
|
3
|
+
*
|
|
4
|
+
* The manager spawns detached process groups and kills them by negative pid
|
|
5
|
+
* (`src/utils/process-group.ts`), which is a POSIX-only operation. On Windows
|
|
6
|
+
* there is no process-group kill and detached spawn semantics differ, so the
|
|
7
|
+
* extensions intentionally no-op there rather than silently misbehaving.
|
|
8
|
+
*
|
|
9
|
+
* Main (0.9.4) shipped the same guard. The rewrite had dropped it.
|
|
10
|
+
*/
|
|
11
|
+
export function isWindowsPlatform(): boolean {
|
|
12
|
+
return process.platform === "win32";
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Replace a home-directory prefix with `~` for display. */
|
|
5
|
+
export function shortenPath(path: string, home = homedir()): string {
|
|
6
|
+
if (path === home) return "~";
|
|
7
|
+
if (path.startsWith(`${home}${sep}`)) {
|
|
8
|
+
return `~${path.slice(home.length)}`;
|
|
9
|
+
}
|
|
10
|
+
return path;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProcessSignalInfo } from "../types";
|
|
2
|
+
|
|
3
|
+
const SIGNALS: Partial<
|
|
4
|
+
Record<NodeJS.Signals, { number: number; description: string }>
|
|
5
|
+
> = {
|
|
6
|
+
SIGINT: { number: 2, description: "interrupt" },
|
|
7
|
+
SIGTERM: { number: 15, description: "termination request" },
|
|
8
|
+
SIGKILL: { number: 9, description: "forced kill" },
|
|
9
|
+
SIGHUP: { number: 1, description: "terminal hangup" },
|
|
10
|
+
SIGQUIT: { number: 3, description: "quit" },
|
|
11
|
+
SIGABRT: { number: 6, description: "abort" },
|
|
12
|
+
SIGALRM: { number: 14, description: "alarm" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function formatSignalInfo(signal: NodeJS.Signals): ProcessSignalInfo {
|
|
16
|
+
const known = SIGNALS[signal];
|
|
17
|
+
return {
|
|
18
|
+
name: signal,
|
|
19
|
+
number: known?.number ?? null,
|
|
20
|
+
description: known?.description ?? signal,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { ProcessManager } from "../../manager";
|
|
3
|
-
|
|
4
|
-
export function registerPsClearCommand(
|
|
5
|
-
pi: ExtensionAPI,
|
|
6
|
-
manager: ProcessManager,
|
|
7
|
-
): void {
|
|
8
|
-
pi.registerCommand("ps:clear", {
|
|
9
|
-
description: "Remove all finished processes from the list",
|
|
10
|
-
handler: async (_args, _ctx) => {
|
|
11
|
-
manager.clearFinished();
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerPsClearCommand } from "./command";
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { ProcessManager } from "../manager";
|
|
2
|
-
|
|
3
|
-
export function runningProcessCompletions(manager: ProcessManager) {
|
|
4
|
-
return (prefix: string) => {
|
|
5
|
-
const processes = manager.list();
|
|
6
|
-
const lower = prefix.toLowerCase();
|
|
7
|
-
return processes
|
|
8
|
-
.filter(
|
|
9
|
-
(p) =>
|
|
10
|
-
p.status === "running" &&
|
|
11
|
-
(p.id.toLowerCase().startsWith(lower) ||
|
|
12
|
-
p.name.toLowerCase().startsWith(lower)),
|
|
13
|
-
)
|
|
14
|
-
.map((p) => ({
|
|
15
|
-
value: p.id,
|
|
16
|
-
label: p.id,
|
|
17
|
-
description: p.name,
|
|
18
|
-
}));
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function allProcessCompletions(manager: ProcessManager) {
|
|
23
|
-
return (prefix: string) => {
|
|
24
|
-
const processes = manager.list();
|
|
25
|
-
const lower = prefix.toLowerCase();
|
|
26
|
-
return processes
|
|
27
|
-
.filter(
|
|
28
|
-
(p) =>
|
|
29
|
-
p.id.toLowerCase().startsWith(lower) ||
|
|
30
|
-
p.name.toLowerCase().startsWith(lower),
|
|
31
|
-
)
|
|
32
|
-
.map((p) => ({
|
|
33
|
-
value: p.id,
|
|
34
|
-
label: p.id,
|
|
35
|
-
description: p.name,
|
|
36
|
-
}));
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { DockActions } from "../../hooks/widget";
|
|
3
|
-
|
|
4
|
-
export function registerPsDockCommand(
|
|
5
|
-
pi: ExtensionAPI,
|
|
6
|
-
dockActions: DockActions,
|
|
7
|
-
): void {
|
|
8
|
-
pi.registerCommand("ps:dock", {
|
|
9
|
-
description: "Control dock visibility",
|
|
10
|
-
getArgumentCompletions: () => [
|
|
11
|
-
{ value: "show", label: "show — make the dock visible" },
|
|
12
|
-
{ value: "hide", label: "hide — hide the dock" },
|
|
13
|
-
{ value: "toggle", label: "toggle — cycle visibility" },
|
|
14
|
-
],
|
|
15
|
-
handler: async (args, _ctx) => {
|
|
16
|
-
const arg = args.trim().toLowerCase();
|
|
17
|
-
|
|
18
|
-
if (arg === "show") {
|
|
19
|
-
dockActions.expand();
|
|
20
|
-
} else if (arg === "hide") {
|
|
21
|
-
dockActions.hide();
|
|
22
|
-
} else if (arg === "toggle" || arg === "") {
|
|
23
|
-
dockActions.toggle();
|
|
24
|
-
} else {
|
|
25
|
-
dockActions.toggle();
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerPsDockCommand } from "./command";
|
package/src/commands/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Process commands with /ps: prefix.
|
|
3
|
-
*
|
|
4
|
-
* /ps - View and manage all background processes
|
|
5
|
-
* /ps:logs [id] - Open log viewer overlay (search, scroll, stream filter)
|
|
6
|
-
* /ps:pin [id] - Pin the dock to a specific process
|
|
7
|
-
* /ps:kill [id] - Kill a running process
|
|
8
|
-
* /ps:clear - Remove all finished processes from the list
|
|
9
|
-
* /ps:dock [show|hide|toggle] - Control dock visibility
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import type { DockActions } from "../hooks/widget";
|
|
14
|
-
import type { ProcessManager } from "../manager";
|
|
15
|
-
import { registerPsClearCommand } from "./clear";
|
|
16
|
-
import { registerPsDockCommand } from "./dock";
|
|
17
|
-
import { registerPsKillCommand } from "./kill";
|
|
18
|
-
import { registerPsLogsCommand } from "./logs";
|
|
19
|
-
import { registerPsPinCommand } from "./pin";
|
|
20
|
-
import { registerPsCommand } from "./processes";
|
|
21
|
-
|
|
22
|
-
export function setupProcessesCommands(
|
|
23
|
-
pi: ExtensionAPI,
|
|
24
|
-
manager: ProcessManager,
|
|
25
|
-
dockActions: DockActions,
|
|
26
|
-
): void {
|
|
27
|
-
registerPsCommand(pi, manager, dockActions);
|
|
28
|
-
registerPsPinCommand(pi, manager, dockActions);
|
|
29
|
-
registerPsLogsCommand(pi, manager);
|
|
30
|
-
registerPsKillCommand(pi, manager, dockActions);
|
|
31
|
-
registerPsClearCommand(pi, manager);
|
|
32
|
-
registerPsDockCommand(pi, dockActions);
|
|
33
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { LIVE_STATUSES } from "../../constants";
|
|
3
|
-
import type { DockActions } from "../../hooks/widget";
|
|
4
|
-
import type { ProcessManager } from "../../manager";
|
|
5
|
-
import { runningProcessCompletions } from "../completions";
|
|
6
|
-
import { pickProcess } from "../pick-process";
|
|
7
|
-
|
|
8
|
-
export function registerPsKillCommand(
|
|
9
|
-
pi: ExtensionAPI,
|
|
10
|
-
manager: ProcessManager,
|
|
11
|
-
dockActions: DockActions,
|
|
12
|
-
): void {
|
|
13
|
-
pi.registerCommand("ps:kill", {
|
|
14
|
-
description: "Kill a running process",
|
|
15
|
-
getArgumentCompletions: runningProcessCompletions(manager),
|
|
16
|
-
handler: async (args, ctx) => {
|
|
17
|
-
const arg = args.trim();
|
|
18
|
-
|
|
19
|
-
let processId: string | undefined;
|
|
20
|
-
|
|
21
|
-
if (arg) {
|
|
22
|
-
const proc = manager.get(arg);
|
|
23
|
-
if (!proc) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (!LIVE_STATUSES.has(proc.status)) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
processId = proc.id;
|
|
30
|
-
} else {
|
|
31
|
-
const running = manager
|
|
32
|
-
.list()
|
|
33
|
-
.filter((p) => LIVE_STATUSES.has(p.status));
|
|
34
|
-
|
|
35
|
-
if (running.length === 0) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (running.length === 1 && running[0]) {
|
|
40
|
-
processId = running[0].id;
|
|
41
|
-
} else {
|
|
42
|
-
processId = await pickProcess(
|
|
43
|
-
ctx,
|
|
44
|
-
manager,
|
|
45
|
-
"Select process to kill",
|
|
46
|
-
(p) => LIVE_STATUSES.has(p.status),
|
|
47
|
-
);
|
|
48
|
-
if (!processId) return;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!processId) return;
|
|
53
|
-
|
|
54
|
-
const proc = manager.get(processId);
|
|
55
|
-
if (!proc) return;
|
|
56
|
-
|
|
57
|
-
const signal =
|
|
58
|
-
proc.status === "terminate_timeout" ? "SIGKILL" : "SIGTERM";
|
|
59
|
-
const timeoutMs = signal === "SIGKILL" ? 200 : 3000;
|
|
60
|
-
const result = await manager.kill(proc.id, { signal, timeoutMs });
|
|
61
|
-
|
|
62
|
-
if (result.ok) {
|
|
63
|
-
if (dockActions.getFocusedProcessId() === proc.id) {
|
|
64
|
-
dockActions.setFocus(null);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerPsKillCommand } from "./command";
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { LogOverlayComponent } from "../../components/log-overlay-component";
|
|
3
|
-
import type { ProcessManager } from "../../manager";
|
|
4
|
-
import { allProcessCompletions } from "../completions";
|
|
5
|
-
|
|
6
|
-
export function registerPsLogsCommand(
|
|
7
|
-
pi: ExtensionAPI,
|
|
8
|
-
manager: ProcessManager,
|
|
9
|
-
): void {
|
|
10
|
-
pi.registerCommand("ps:logs", {
|
|
11
|
-
description:
|
|
12
|
-
"Open log viewer for a process (search, scroll, stream filter)",
|
|
13
|
-
getArgumentCompletions: allProcessCompletions(manager),
|
|
14
|
-
handler: async (args, ctx) => {
|
|
15
|
-
if (!ctx.hasUI) return;
|
|
16
|
-
|
|
17
|
-
const arg = args.trim();
|
|
18
|
-
let processId: string | undefined;
|
|
19
|
-
|
|
20
|
-
if (arg) {
|
|
21
|
-
const proc = manager.get(arg);
|
|
22
|
-
if (!proc) return;
|
|
23
|
-
processId = proc.id;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await ctx.ui.custom<null>(
|
|
27
|
-
(_tui, theme, _kb, done) => {
|
|
28
|
-
return new LogOverlayComponent({
|
|
29
|
-
tui: _tui,
|
|
30
|
-
theme,
|
|
31
|
-
manager,
|
|
32
|
-
initialProcessId: processId,
|
|
33
|
-
done: () => done(null),
|
|
34
|
-
});
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
overlay: true,
|
|
38
|
-
overlayOptions: {
|
|
39
|
-
width: "90%",
|
|
40
|
-
maxHeight: "80%",
|
|
41
|
-
anchor: "center",
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
);
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerPsLogsCommand } from "./command";
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { ProcessPickerComponent } from "../components/process-picker-component";
|
|
3
|
-
import type { ProcessInfo } from "../constants";
|
|
4
|
-
import type { ProcessManager } from "../manager";
|
|
5
|
-
|
|
6
|
-
export async function pickProcess(
|
|
7
|
-
ctx: ExtensionCommandContext,
|
|
8
|
-
manager: ProcessManager,
|
|
9
|
-
title: string,
|
|
10
|
-
filter?: (proc: ProcessInfo) => boolean,
|
|
11
|
-
): Promise<string | undefined> {
|
|
12
|
-
if (!ctx.hasUI) {
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
17
|
-
return new ProcessPickerComponent(
|
|
18
|
-
tui,
|
|
19
|
-
theme,
|
|
20
|
-
(processId?: string) => {
|
|
21
|
-
done(processId ?? null);
|
|
22
|
-
},
|
|
23
|
-
manager,
|
|
24
|
-
title,
|
|
25
|
-
filter,
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (result === undefined || result === null) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { DockActions } from "../../hooks/widget";
|
|
3
|
-
import type { ProcessManager } from "../../manager";
|
|
4
|
-
import { allProcessCompletions } from "../completions";
|
|
5
|
-
import { pickProcess } from "../pick-process";
|
|
6
|
-
|
|
7
|
-
export function registerPsPinCommand(
|
|
8
|
-
pi: ExtensionAPI,
|
|
9
|
-
manager: ProcessManager,
|
|
10
|
-
dockActions: DockActions,
|
|
11
|
-
): void {
|
|
12
|
-
pi.registerCommand("ps:pin", {
|
|
13
|
-
description: "Pin the dock to a specific process",
|
|
14
|
-
getArgumentCompletions: allProcessCompletions(manager),
|
|
15
|
-
handler: async (args, ctx) => {
|
|
16
|
-
const arg = args.trim();
|
|
17
|
-
let processId: string | undefined;
|
|
18
|
-
|
|
19
|
-
if (arg) {
|
|
20
|
-
const proc = manager.get(arg);
|
|
21
|
-
if (!proc) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
processId = proc.id;
|
|
25
|
-
} else {
|
|
26
|
-
processId = await pickProcess(ctx, manager, "Select process to pin");
|
|
27
|
-
if (!processId) return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!processId) return;
|
|
31
|
-
dockActions.setFocus(processId);
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { registerPsPinCommand } from "./command";
|