@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.
Files changed (171) hide show
  1. package/CONTRIBUTING.md +40 -42
  2. package/README.md +54 -84
  3. package/extensions/process-tabs.ts +26 -0
  4. package/extensions/processes/client.ts +194 -0
  5. package/extensions/processes/commands/clear.ts +29 -0
  6. package/extensions/processes/commands/kill.ts +152 -0
  7. package/extensions/processes/commands/overview.ts +102 -0
  8. package/extensions/processes/components/overview-component.ts +729 -0
  9. package/extensions/processes/components/overview-panel.ts +177 -0
  10. package/extensions/processes/config/defaults.ts +28 -0
  11. package/extensions/processes/config/index.ts +13 -0
  12. package/extensions/processes/config/loader.ts +62 -0
  13. package/extensions/processes/config/migrations/000-import-legacy-process-config.ts +113 -0
  14. package/extensions/processes/config/migrations/001-v0-9-4-to-v0-10-0-config.ts +184 -0
  15. package/extensions/processes/config/migrations/002-stamp-v0-10-0-config-version.ts +21 -0
  16. package/extensions/processes/config/migrations/index.ts +25 -0
  17. package/extensions/processes/config/schema.ts +9 -0
  18. package/extensions/processes/config/types.ts +72 -0
  19. package/extensions/processes/constants.ts +1 -0
  20. package/extensions/processes/handlers/commands.ts +113 -0
  21. package/extensions/processes/handlers/kill-process.ts +31 -0
  22. package/extensions/processes/handlers/notifications.ts +147 -0
  23. package/extensions/processes/handlers/requests.ts +114 -0
  24. package/extensions/processes/handlers/subscriptions.ts +144 -0
  25. package/extensions/processes/hooks/background-blocker.ts +201 -0
  26. package/extensions/processes/hooks/cleanup.ts +43 -0
  27. package/extensions/processes/hooks/event-bridge.ts +32 -0
  28. package/extensions/processes/i18n/index.ts +3 -0
  29. package/extensions/processes/i18n/messages.ts +27 -0
  30. package/extensions/processes/i18n/translator.ts +49 -0
  31. package/extensions/processes/index.ts +109 -0
  32. package/extensions/processes/message-renderer.ts +117 -0
  33. package/extensions/processes/notification-sender.ts +43 -0
  34. package/extensions/processes/notifications/classify.ts +27 -0
  35. package/extensions/processes/notifications/log-matchers.ts +146 -0
  36. package/extensions/processes/notifications/registry.ts +225 -0
  37. package/extensions/processes/notifications/render-content.ts +122 -0
  38. package/extensions/processes/notifications/service.ts +336 -0
  39. package/extensions/processes/notifications/types.ts +11 -0
  40. package/extensions/processes/settings/apply-setting-change.ts +30 -0
  41. package/extensions/processes/settings/build-sections.ts +420 -0
  42. package/extensions/processes/settings/index.ts +42 -0
  43. package/extensions/processes/tools/clear/index.ts +21 -0
  44. package/extensions/processes/tools/clear/render.ts +40 -0
  45. package/extensions/processes/tools/components/index.ts +4 -0
  46. package/extensions/processes/tools/components/process-action-header.ts +31 -0
  47. package/extensions/processes/tools/components/process-action-title.ts +17 -0
  48. package/extensions/processes/tools/components/tool-layout.ts +70 -0
  49. package/extensions/processes/tools/components/watch.ts +53 -0
  50. package/extensions/processes/tools/index.ts +265 -0
  51. package/extensions/processes/tools/list/index.ts +157 -0
  52. package/extensions/processes/tools/list/render.ts +211 -0
  53. package/extensions/processes/tools/notify.ts +191 -0
  54. package/extensions/processes/tools/output/index.ts +307 -0
  55. package/extensions/processes/tools/output/render.ts +229 -0
  56. package/extensions/processes/tools/schema.ts +263 -0
  57. package/extensions/processes/tools/start/index.ts +64 -0
  58. package/extensions/processes/tools/start/render.ts +67 -0
  59. package/extensions/processes/tools/stop/index.ts +35 -0
  60. package/extensions/processes/tools/stop/render.ts +43 -0
  61. package/extensions/processes/tools/update/index.ts +320 -0
  62. package/extensions/processes/tools/update/render.ts +251 -0
  63. package/extensions/processes/tools/utils.ts +112 -0
  64. package/extensions/processes/tools/watch-format.ts +14 -0
  65. package/extensions/processes/tools/write/index.ts +75 -0
  66. package/extensions/processes/tools/write/render.ts +68 -0
  67. package/extensions/processes/utils/truncate.ts +285 -0
  68. package/extensions/processes-dock/client.ts +60 -0
  69. package/extensions/processes-dock/commands/dock.ts +70 -0
  70. package/extensions/processes-dock/commands/pin.ts +166 -0
  71. package/extensions/processes-dock/components/log-dock-component.ts +375 -0
  72. package/extensions/processes-dock/dock-state.ts +33 -0
  73. package/extensions/processes-dock/index.ts +28 -0
  74. package/extensions/processes-dock/logs-client.ts +5 -0
  75. package/extensions/processes-dock/widget/setup.ts +520 -0
  76. package/extensions/processes-dock/widget/status.ts +149 -0
  77. package/extensions/processes-dock/widget/types.ts +16 -0
  78. package/extensions/processes-logs/client.ts +51 -0
  79. package/extensions/processes-logs/commands/logs.ts +108 -0
  80. package/extensions/processes-logs/completions.ts +31 -0
  81. package/extensions/processes-logs/components/log-file-viewer.ts +274 -0
  82. package/extensions/processes-logs/components/log-overlay-component.ts +685 -0
  83. package/extensions/processes-logs/index.ts +27 -0
  84. package/extensions/processes-logs/logs-client.ts +101 -0
  85. package/extensions/shared/line-buffer.ts +35 -0
  86. package/extensions/shared/output-payload.ts +28 -0
  87. package/extensions/shared/ui.ts +146 -0
  88. package/package.json +47 -20
  89. package/schema.json +134 -0
  90. package/skills/pi-processes/SKILL.md +496 -25
  91. package/src/get-manager.ts +15 -0
  92. package/src/manager/index.ts +182 -0
  93. package/src/manager/internal-types.ts +130 -0
  94. package/src/manager/limits.ts +15 -0
  95. package/src/manager/process-log-store.ts +422 -0
  96. package/src/manager/process-output.ts +345 -0
  97. package/src/manager/process-registry.ts +78 -0
  98. package/src/manager/process-runtime-controller.ts +499 -0
  99. package/src/protocol/broadcasts.ts +14 -0
  100. package/src/protocol/channels.ts +30 -0
  101. package/src/protocol/commands.ts +24 -0
  102. package/src/protocol/index.ts +34 -0
  103. package/src/protocol/logs.ts +23 -0
  104. package/src/protocol/notifications.ts +48 -0
  105. package/src/protocol/requests.ts +76 -0
  106. package/src/types.ts +66 -0
  107. package/src/utils/ansi.ts +2 -4
  108. package/src/utils/format.ts +24 -32
  109. package/src/utils/index.ts +5 -1
  110. package/src/utils/is-record.ts +3 -0
  111. package/src/utils/match-line.ts +32 -0
  112. package/src/utils/platform.ts +13 -0
  113. package/src/utils/shorten-path.ts +11 -0
  114. package/src/utils/signals.ts +22 -0
  115. package/src/commands/clear/command.ts +0 -14
  116. package/src/commands/clear/index.ts +0 -1
  117. package/src/commands/completions.ts +0 -38
  118. package/src/commands/dock/command.ts +0 -29
  119. package/src/commands/dock/index.ts +0 -1
  120. package/src/commands/index.ts +0 -33
  121. package/src/commands/kill/command.ts +0 -69
  122. package/src/commands/kill/index.ts +0 -1
  123. package/src/commands/logs/command.ts +0 -47
  124. package/src/commands/logs/index.ts +0 -1
  125. package/src/commands/pick-process.ts +0 -34
  126. package/src/commands/pin/command.ts +0 -34
  127. package/src/commands/pin/index.ts +0 -1
  128. package/src/commands/processes/command.ts +0 -39
  129. package/src/commands/processes/index.ts +0 -1
  130. package/src/commands/settings/apply-setting-change.ts +0 -72
  131. package/src/commands/settings/build-sections.ts +0 -160
  132. package/src/commands/settings/command.ts +0 -20
  133. package/src/commands/settings/index.ts +0 -1
  134. package/src/components/log-dock-component.test.ts +0 -29
  135. package/src/components/log-dock-component.ts +0 -236
  136. package/src/components/log-file-viewer.ts +0 -317
  137. package/src/components/log-overlay-component.ts +0 -546
  138. package/src/components/panel-helpers.ts +0 -37
  139. package/src/components/process-picker-component.ts +0 -166
  140. package/src/components/processes-component.ts +0 -485
  141. package/src/components/status-format.ts +0 -38
  142. package/src/config.ts +0 -107
  143. package/src/constants/index.ts +0 -16
  144. package/src/constants/types.ts +0 -106
  145. package/src/hooks/background-blocker.ts +0 -66
  146. package/src/hooks/cleanup.ts +0 -10
  147. package/src/hooks/index.ts +0 -32
  148. package/src/hooks/message-renderer.ts +0 -115
  149. package/src/hooks/process-end.ts +0 -69
  150. package/src/hooks/process-watch.ts +0 -85
  151. package/src/hooks/utils.ts +0 -23
  152. package/src/hooks/widget/index.ts +0 -2
  153. package/src/hooks/widget/setup.ts +0 -167
  154. package/src/hooks/widget/status-widget.ts +0 -105
  155. package/src/hooks/widget/types.ts +0 -20
  156. package/src/index.ts +0 -35
  157. package/src/manager.test.ts +0 -331
  158. package/src/manager.ts +0 -758
  159. package/src/tools/actions/clear.ts +0 -20
  160. package/src/tools/actions/debug.ts +0 -148
  161. package/src/tools/actions/index.ts +0 -171
  162. package/src/tools/actions/kill.ts +0 -89
  163. package/src/tools/actions/list.ts +0 -188
  164. package/src/tools/actions/logs.ts +0 -117
  165. package/src/tools/actions/output.ts +0 -269
  166. package/src/tools/actions/start.ts +0 -245
  167. package/src/tools/actions/write.ts +0 -113
  168. package/src/tools/index.ts +0 -207
  169. package/src/utils/ansi.test.ts +0 -22
  170. package/src/utils/command-executor.test.ts +0 -48
  171. 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}\\[[0-9;]*[A-Za-z]`, "gu"),
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
  }
@@ -1,12 +1,16 @@
1
- import type { Theme } from "@earendil-works/pi-coding-agent";
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 ?? Date.now();
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
- export function formatStatus(proc: ProcessInfo): string {
24
- switch (proc.status) {
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
- }
@@ -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,3 @@
1
+ export function isRecord(value: unknown): value is Record<string, unknown> {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
@@ -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";
@@ -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";