@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,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build settings sections for the processes package.
|
|
3
|
+
*
|
|
4
|
+
* The first-level view stays focused. Extension-specific groups open focused
|
|
5
|
+
* detail panels instead of expanding every setting into one long list.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type Scope,
|
|
10
|
+
SettingsDetailEditor,
|
|
11
|
+
type SettingsSection,
|
|
12
|
+
type SettingsTheme,
|
|
13
|
+
} from "@aliou/pi-utils-settings";
|
|
14
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
15
|
+
|
|
16
|
+
import type { ProcessConfig, ProcessProtocolConfig } from "../config";
|
|
17
|
+
|
|
18
|
+
interface BuildSectionsContext {
|
|
19
|
+
setDraft: (config: ProcessConfig) => void;
|
|
20
|
+
scope: Scope;
|
|
21
|
+
isInherited: (path: string) => boolean;
|
|
22
|
+
theme: SettingsTheme;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildSections(
|
|
26
|
+
tabConfig: ProcessConfig | null,
|
|
27
|
+
resolved: ProcessProtocolConfig,
|
|
28
|
+
ctx: BuildSectionsContext,
|
|
29
|
+
): SettingsSection[] {
|
|
30
|
+
const scopedConfig = structuredClone(tabConfig ?? {}) as ProcessConfig;
|
|
31
|
+
|
|
32
|
+
const coreSection: SettingsSection = {
|
|
33
|
+
label: "Core",
|
|
34
|
+
items: [
|
|
35
|
+
buildShellPathItem(scopedConfig, resolved, ctx),
|
|
36
|
+
boolItem(
|
|
37
|
+
"interception.blockBackgroundCommands",
|
|
38
|
+
"Block background commands",
|
|
39
|
+
"Block shell background patterns (&, nohup, disown, setsid) in bash tool calls. Redirects to the process tool instead.",
|
|
40
|
+
scopedConfig.interception?.blockBackgroundCommands,
|
|
41
|
+
resolved.interception.blockBackgroundCommands,
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const logsSection: SettingsSection = {
|
|
47
|
+
label: "Interfaces",
|
|
48
|
+
items: [
|
|
49
|
+
boolItem(
|
|
50
|
+
"widget.showStatusWidget",
|
|
51
|
+
"Status widget",
|
|
52
|
+
"Show a one-line summary of managed processes below the editor.",
|
|
53
|
+
scopedConfig.widget?.showStatusWidget,
|
|
54
|
+
resolved.widget.showStatusWidget,
|
|
55
|
+
),
|
|
56
|
+
buildOverviewDetailItem(scopedConfig, resolved, ctx),
|
|
57
|
+
buildLogsDetailItem(scopedConfig, resolved, ctx),
|
|
58
|
+
buildDockDetailItem(scopedConfig, resolved, ctx),
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return [coreSection, logsSection];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildShellPathItem(
|
|
66
|
+
scopedConfig: ProcessConfig,
|
|
67
|
+
resolved: ProcessProtocolConfig,
|
|
68
|
+
ctx: BuildSectionsContext,
|
|
69
|
+
): SettingItem {
|
|
70
|
+
const shellPath =
|
|
71
|
+
scopedConfig.execution?.shellPath ?? resolved.execution.shellPath ?? "";
|
|
72
|
+
const display = scopedConfig.execution?.shellPath
|
|
73
|
+
? scopedConfig.execution.shellPath
|
|
74
|
+
: resolved.execution.shellPath
|
|
75
|
+
? `inherited: ${resolved.execution.shellPath}`
|
|
76
|
+
: "(default)";
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: "execution.shellPath.details",
|
|
80
|
+
label: "Shell path",
|
|
81
|
+
currentValue: display,
|
|
82
|
+
description:
|
|
83
|
+
"Open focused settings for the shell executable used by process commands.",
|
|
84
|
+
submenu: (_current, done) => {
|
|
85
|
+
const current = scopedConfig;
|
|
86
|
+
let nextShellPath = shellPath;
|
|
87
|
+
|
|
88
|
+
const syncDraft = () => {
|
|
89
|
+
ctx.setDraft({
|
|
90
|
+
...current,
|
|
91
|
+
execution: {
|
|
92
|
+
...current.execution,
|
|
93
|
+
shellPath: nextShellPath,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return new SettingsDetailEditor({
|
|
99
|
+
title: "Shell path",
|
|
100
|
+
theme: ctx.theme,
|
|
101
|
+
fields: [
|
|
102
|
+
{
|
|
103
|
+
id: "execution.shellPath.value",
|
|
104
|
+
type: "text",
|
|
105
|
+
label: "Shell path",
|
|
106
|
+
description:
|
|
107
|
+
"Path to the shell executable. Leave empty to use the system default.",
|
|
108
|
+
getValue: () => nextShellPath,
|
|
109
|
+
setValue: (value) => {
|
|
110
|
+
nextShellPath = value;
|
|
111
|
+
syncDraft();
|
|
112
|
+
},
|
|
113
|
+
displayValue: (value) => value || "(default)",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
getDoneSummary: () => nextShellPath || "(default)",
|
|
117
|
+
onDone: (summary) => done(summary),
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildOverviewDetailItem(
|
|
124
|
+
scopedConfig: ProcessConfig,
|
|
125
|
+
resolved: ProcessProtocolConfig,
|
|
126
|
+
ctx: BuildSectionsContext,
|
|
127
|
+
): SettingItem {
|
|
128
|
+
const maxVisibleProcesses =
|
|
129
|
+
scopedConfig.processList?.maxVisibleProcesses ??
|
|
130
|
+
resolved.processList.maxVisibleProcesses;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
id: "overview.details",
|
|
134
|
+
label: "Overview panel",
|
|
135
|
+
currentValue: `${maxVisibleProcesses} rows`,
|
|
136
|
+
description:
|
|
137
|
+
"Open focused settings for the /ps overview panel visible row count.",
|
|
138
|
+
submenu: (_current, done) => {
|
|
139
|
+
const current = scopedConfig;
|
|
140
|
+
let nextMaxVisible = String(maxVisibleProcesses);
|
|
141
|
+
|
|
142
|
+
const syncDraft = () => {
|
|
143
|
+
const updated: ProcessConfig = {
|
|
144
|
+
...current,
|
|
145
|
+
processList: {
|
|
146
|
+
...current.processList,
|
|
147
|
+
maxVisibleProcesses: parsePositiveInt(nextMaxVisible),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
ctx.setDraft(updated);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return new SettingsDetailEditor({
|
|
154
|
+
title: "Overview panel",
|
|
155
|
+
theme: ctx.theme,
|
|
156
|
+
fields: [
|
|
157
|
+
{
|
|
158
|
+
id: "overview.maxVisibleProcesses",
|
|
159
|
+
type: "text",
|
|
160
|
+
label: "Visible rows",
|
|
161
|
+
description:
|
|
162
|
+
"Maximum process rows shown at once in the /ps overview. The list scrolls past this.",
|
|
163
|
+
getValue: () => nextMaxVisible,
|
|
164
|
+
setValue: (value) => {
|
|
165
|
+
nextMaxVisible = value;
|
|
166
|
+
syncDraft();
|
|
167
|
+
},
|
|
168
|
+
validate: positiveIntegerError,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
getDoneSummary: () => `${parsePositiveInt(nextMaxVisible)} rows`,
|
|
172
|
+
onDone: (summary) => done(summary),
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function buildLogsDetailItem(
|
|
179
|
+
scopedConfig: ProcessConfig,
|
|
180
|
+
resolved: ProcessProtocolConfig,
|
|
181
|
+
ctx: BuildSectionsContext,
|
|
182
|
+
): SettingItem {
|
|
183
|
+
const viewportRows =
|
|
184
|
+
scopedConfig.processList?.maxPreviewLines ??
|
|
185
|
+
resolved.processList.maxPreviewLines;
|
|
186
|
+
const historyLines =
|
|
187
|
+
scopedConfig.output?.maxOutputLines ?? resolved.output.maxOutputLines;
|
|
188
|
+
const historyBudgetMb = Math.max(
|
|
189
|
+
1,
|
|
190
|
+
Math.round(
|
|
191
|
+
(scopedConfig.output?.maxOutputBytes ?? resolved.output.maxOutputBytes) /
|
|
192
|
+
(1024 * 1024),
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
const followByDefault =
|
|
196
|
+
scopedConfig.follow?.enabledByDefault ?? resolved.follow.enabledByDefault;
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
id: "logs.details",
|
|
200
|
+
label: "Logs overlay",
|
|
201
|
+
currentValue: `${historyLines} lines · ${historyBudgetMb} MB · ${viewportRows} rows · ${followByDefault ? "follow" : "manual"}`,
|
|
202
|
+
description:
|
|
203
|
+
"Open focused settings for /ps:logs tabs, history, viewport, and follow behavior.",
|
|
204
|
+
submenu: (_current, done) => {
|
|
205
|
+
const current = scopedConfig;
|
|
206
|
+
let nextViewportRows = String(viewportRows);
|
|
207
|
+
let nextHistoryLines = String(historyLines);
|
|
208
|
+
let nextHistoryBudgetMb = String(historyBudgetMb);
|
|
209
|
+
let historyBudgetEdited = false;
|
|
210
|
+
let nextFollowByDefault = followByDefault;
|
|
211
|
+
let nextAutoHideOnFinish =
|
|
212
|
+
scopedConfig.follow?.autoHideOnFinish ??
|
|
213
|
+
resolved.follow.autoHideOnFinish;
|
|
214
|
+
|
|
215
|
+
const syncDraft = () => {
|
|
216
|
+
const output = {
|
|
217
|
+
...current.output,
|
|
218
|
+
maxOutputLines: parsePositiveInt(nextHistoryLines),
|
|
219
|
+
};
|
|
220
|
+
if (historyBudgetEdited) {
|
|
221
|
+
output.maxOutputBytes =
|
|
222
|
+
parsePositiveInt(nextHistoryBudgetMb) * 1024 * 1024;
|
|
223
|
+
}
|
|
224
|
+
const updated: ProcessConfig = {
|
|
225
|
+
...current,
|
|
226
|
+
processList: {
|
|
227
|
+
...current.processList,
|
|
228
|
+
maxPreviewLines: parsePositiveInt(nextViewportRows),
|
|
229
|
+
},
|
|
230
|
+
output,
|
|
231
|
+
follow: {
|
|
232
|
+
...current.follow,
|
|
233
|
+
enabledByDefault: nextFollowByDefault,
|
|
234
|
+
autoHideOnFinish: nextAutoHideOnFinish,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
ctx.setDraft(updated);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return new SettingsDetailEditor({
|
|
241
|
+
title: "Logs overlay",
|
|
242
|
+
theme: ctx.theme,
|
|
243
|
+
fields: [
|
|
244
|
+
{
|
|
245
|
+
id: "logs.viewportRows",
|
|
246
|
+
type: "text",
|
|
247
|
+
label: "Viewport rows",
|
|
248
|
+
description:
|
|
249
|
+
"Maximum log rows rendered in the /ps:logs overlay body.",
|
|
250
|
+
getValue: () => nextViewportRows,
|
|
251
|
+
setValue: (value) => {
|
|
252
|
+
nextViewportRows = value;
|
|
253
|
+
syncDraft();
|
|
254
|
+
},
|
|
255
|
+
validate: positiveIntegerError,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "logs.historyLines",
|
|
259
|
+
type: "text",
|
|
260
|
+
label: "History lines",
|
|
261
|
+
description:
|
|
262
|
+
"Maximum existing log lines loaded when opening a process log.",
|
|
263
|
+
getValue: () => nextHistoryLines,
|
|
264
|
+
setValue: (value) => {
|
|
265
|
+
nextHistoryLines = value;
|
|
266
|
+
syncDraft();
|
|
267
|
+
},
|
|
268
|
+
validate: positiveIntegerError,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "logs.historyBudgetMb",
|
|
272
|
+
type: "text",
|
|
273
|
+
label: "History budget (MB)",
|
|
274
|
+
description:
|
|
275
|
+
"Maximum in-memory log history retained per process view.",
|
|
276
|
+
getValue: () => nextHistoryBudgetMb,
|
|
277
|
+
setValue: (value) => {
|
|
278
|
+
nextHistoryBudgetMb = value;
|
|
279
|
+
historyBudgetEdited = true;
|
|
280
|
+
syncDraft();
|
|
281
|
+
},
|
|
282
|
+
validate: positiveIntegerError,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "logs.followByDefault",
|
|
286
|
+
type: "boolean",
|
|
287
|
+
label: "Follow by default",
|
|
288
|
+
description: "Open process logs pinned to the latest output.",
|
|
289
|
+
getValue: () => nextFollowByDefault,
|
|
290
|
+
setValue: (value) => {
|
|
291
|
+
nextFollowByDefault = value;
|
|
292
|
+
syncDraft();
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: "logs.autoHideOnFinish",
|
|
297
|
+
type: "boolean",
|
|
298
|
+
label: "Auto-hide on finish",
|
|
299
|
+
description:
|
|
300
|
+
"Close the logs overlay when all processes have finished.",
|
|
301
|
+
getValue: () => nextAutoHideOnFinish,
|
|
302
|
+
setValue: (value) => {
|
|
303
|
+
nextAutoHideOnFinish = value;
|
|
304
|
+
syncDraft();
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
getDoneSummary: () =>
|
|
309
|
+
`${parsePositiveInt(nextHistoryLines)} lines · ${parsePositiveInt(nextHistoryBudgetMb)} MB · ${parsePositiveInt(nextViewportRows)} rows · ${nextFollowByDefault ? "follow" : "manual"}`,
|
|
310
|
+
onDone: (summary) => done(summary),
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function buildDockDetailItem(
|
|
317
|
+
scopedConfig: ProcessConfig,
|
|
318
|
+
resolved: ProcessProtocolConfig,
|
|
319
|
+
ctx: BuildSectionsContext,
|
|
320
|
+
): SettingItem {
|
|
321
|
+
const dockDefaultState =
|
|
322
|
+
scopedConfig.widget?.dockDefaultState ?? resolved.widget.dockDefaultState;
|
|
323
|
+
const dockHeight =
|
|
324
|
+
scopedConfig.widget?.dockHeight ?? resolved.widget.dockHeight;
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
id: "dock.details",
|
|
328
|
+
label: "Dock",
|
|
329
|
+
currentValue: `${dockDefaultState} · ${dockHeight} log lines`,
|
|
330
|
+
description:
|
|
331
|
+
"Open focused settings for the /ps:dock widget above the editor.",
|
|
332
|
+
submenu: (_current, done) => {
|
|
333
|
+
const current = scopedConfig;
|
|
334
|
+
let nextDockDefaultState = dockDefaultState;
|
|
335
|
+
let nextDockHeight = String(dockHeight);
|
|
336
|
+
|
|
337
|
+
const syncDraft = () => {
|
|
338
|
+
ctx.setDraft({
|
|
339
|
+
...current,
|
|
340
|
+
widget: {
|
|
341
|
+
...current.widget,
|
|
342
|
+
dockDefaultState: nextDockDefaultState,
|
|
343
|
+
dockHeight: parsePositiveInt(nextDockHeight),
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
return new SettingsDetailEditor({
|
|
349
|
+
title: "Dock",
|
|
350
|
+
theme: ctx.theme,
|
|
351
|
+
fields: [
|
|
352
|
+
{
|
|
353
|
+
id: "dock.defaultState",
|
|
354
|
+
type: "enum",
|
|
355
|
+
label: "Default state",
|
|
356
|
+
description: "Initial state, also used when a new process starts.",
|
|
357
|
+
options: ["closed", "collapsed", "expanded"],
|
|
358
|
+
getValue: () => nextDockDefaultState,
|
|
359
|
+
setValue: (value) => {
|
|
360
|
+
nextDockDefaultState = value as
|
|
361
|
+
| "closed"
|
|
362
|
+
| "collapsed"
|
|
363
|
+
| "expanded";
|
|
364
|
+
syncDraft();
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: "dock.height",
|
|
369
|
+
type: "text",
|
|
370
|
+
label: "Log lines displayed",
|
|
371
|
+
description:
|
|
372
|
+
"Maximum log/content rows shown inside the dock. Border, title, process strip, and separator are added on top.",
|
|
373
|
+
getValue: () => nextDockHeight,
|
|
374
|
+
setValue: (value) => {
|
|
375
|
+
nextDockHeight = value;
|
|
376
|
+
syncDraft();
|
|
377
|
+
},
|
|
378
|
+
validate: positiveIntegerError,
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
getDoneSummary: () =>
|
|
382
|
+
`${nextDockDefaultState} · ${parsePositiveInt(nextDockHeight)} log lines`,
|
|
383
|
+
onDone: (summary) => done(summary),
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function boolItem(
|
|
390
|
+
id: string,
|
|
391
|
+
label: string,
|
|
392
|
+
description: string,
|
|
393
|
+
scopedValue: boolean | undefined,
|
|
394
|
+
resolvedValue: boolean,
|
|
395
|
+
): SettingItem {
|
|
396
|
+
const display =
|
|
397
|
+
scopedValue === undefined
|
|
398
|
+
? `inherited: ${resolvedValue ? "on" : "off"}`
|
|
399
|
+
: scopedValue
|
|
400
|
+
? "on"
|
|
401
|
+
: "off";
|
|
402
|
+
return {
|
|
403
|
+
id,
|
|
404
|
+
label,
|
|
405
|
+
description,
|
|
406
|
+
currentValue: display,
|
|
407
|
+
values: ["on", "off"],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function positiveIntegerError(value: string): string | null {
|
|
412
|
+
return Number.isInteger(Number(value)) && Number(value) > 0
|
|
413
|
+
? null
|
|
414
|
+
: "Enter a positive integer";
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function parsePositiveInt(value: string): number {
|
|
418
|
+
const parsed = Number.parseInt(value, 10);
|
|
419
|
+
return Number.isNaN(parsed) || parsed <= 0 ? 1 : parsed;
|
|
420
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings registration for the processes extension.
|
|
3
|
+
*
|
|
4
|
+
* Uses @aliou/pi-utils-settings infrastructure for a /ps:settings command
|
|
5
|
+
* with Global/Local/Memory tabs and sectioned settings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
registerSettingsCommand,
|
|
10
|
+
type SettingsSection,
|
|
11
|
+
} from "@aliou/pi-utils-settings";
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
|
|
14
|
+
import type { ProcessConfig, ProcessProtocolConfig } from "../config";
|
|
15
|
+
import { createSettingsConfigStore } from "../config";
|
|
16
|
+
import { applySettingChange } from "./apply-setting-change";
|
|
17
|
+
import { buildSections } from "./build-sections";
|
|
18
|
+
|
|
19
|
+
export function registerProcessSettings(pi: ExtensionAPI): void {
|
|
20
|
+
const configStore = createSettingsConfigStore();
|
|
21
|
+
|
|
22
|
+
registerSettingsCommand<ProcessConfig, ProcessProtocolConfig>(pi, {
|
|
23
|
+
commandName: "ps:settings",
|
|
24
|
+
title: "Processes Settings",
|
|
25
|
+
configStore,
|
|
26
|
+
buildSections: (
|
|
27
|
+
tabConfig: ProcessConfig | null,
|
|
28
|
+
resolved: ProcessProtocolConfig,
|
|
29
|
+
ctx,
|
|
30
|
+
): SettingsSection[] => {
|
|
31
|
+
return buildSections(tabConfig, resolved, {
|
|
32
|
+
setDraft: ctx.setDraft,
|
|
33
|
+
scope: ctx.scope,
|
|
34
|
+
isInherited: ctx.isInherited,
|
|
35
|
+
theme: ctx.theme,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
onSettingChange: (id, newValue, config) => {
|
|
39
|
+
return applySettingChange(id, newValue, config);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProcessManager } from "../../../../src/manager";
|
|
2
|
+
|
|
3
|
+
export interface ClearDetails {
|
|
4
|
+
action: "clear";
|
|
5
|
+
cleared: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function executeClear(manager: ProcessManager): ClearDetails {
|
|
9
|
+
return {
|
|
10
|
+
action: "clear",
|
|
11
|
+
cleared: manager.clearFinished(),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatClearDetails(details: ClearDetails): string {
|
|
16
|
+
if (details.cleared === 0) {
|
|
17
|
+
return "No finished background processes to clear.";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `Cleared ${details.cleared} finished background ${details.cleared === 1 ? "process" : "processes"}.`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { ProcessActionTitle } from "../components";
|
|
4
|
+
import type { ProcessesParamsType } from "../schema";
|
|
5
|
+
import { formatCount } from "../utils";
|
|
6
|
+
import type { ClearDetails } from ".";
|
|
7
|
+
|
|
8
|
+
export function buildHeader(
|
|
9
|
+
_args: ProcessesParamsType,
|
|
10
|
+
theme: Theme,
|
|
11
|
+
): Container {
|
|
12
|
+
const header = new Container();
|
|
13
|
+
header.addChild(new ProcessActionTitle("clear", theme));
|
|
14
|
+
return header;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildExpanded(details: ClearDetails, theme: Theme): Container {
|
|
18
|
+
return buildCollapsed(details, theme);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildCollapsed(details: ClearDetails, theme: Theme): Container {
|
|
22
|
+
const container = new Container();
|
|
23
|
+
const tone = details.cleared > 0 ? "success" : "muted";
|
|
24
|
+
container.addChild(
|
|
25
|
+
new Text(
|
|
26
|
+
theme.fg(tone, `${formatCount(details.cleared, "process")} cleared`),
|
|
27
|
+
0,
|
|
28
|
+
0,
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
return container;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildFooter(
|
|
35
|
+
_details: ClearDetails,
|
|
36
|
+
_options: { expanded?: boolean },
|
|
37
|
+
_theme: Theme,
|
|
38
|
+
): Container | null {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Spacer } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { ProcessesParamsType } from "../schema";
|
|
4
|
+
import { buildCommandField } from "../utils";
|
|
5
|
+
import { ProcessActionTitle } from "./process-action-title";
|
|
6
|
+
|
|
7
|
+
export class ProcessActionHeader extends Container {
|
|
8
|
+
constructor(
|
|
9
|
+
args: ProcessesParamsType,
|
|
10
|
+
theme: Theme,
|
|
11
|
+
options: { action: string; suffix?: string; expanded?: boolean },
|
|
12
|
+
) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.addChild(
|
|
16
|
+
new ProcessActionTitle(options.action, theme, options.suffix),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (args.command) {
|
|
20
|
+
if (options.expanded) {
|
|
21
|
+
this.addChild(new Spacer(1));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.addChild(
|
|
25
|
+
buildCommandField(args.command, theme, {
|
|
26
|
+
truncate: !options.expanded,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export class ProcessActionTitle extends Text {
|
|
5
|
+
constructor(action: string, theme: Theme, suffix?: string) {
|
|
6
|
+
const parts = [
|
|
7
|
+
theme.fg("toolTitle", theme.bold("Process:")),
|
|
8
|
+
theme.fg("muted", action),
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
if (suffix) {
|
|
12
|
+
parts.push(suffix);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
super(parts.join(" "), 0, 0);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type Component, Spacer } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export class ToolLayout implements Component {
|
|
4
|
+
private header: Component | null = null;
|
|
5
|
+
private body: Component | null = null;
|
|
6
|
+
private footer: Component | null = null;
|
|
7
|
+
private spacing = true;
|
|
8
|
+
|
|
9
|
+
setHeader(component: Component | null): this {
|
|
10
|
+
this.header = component;
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setBody(component: Component | null): this {
|
|
15
|
+
this.body = component;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setFooter(component: Component | null): this {
|
|
20
|
+
this.footer = component;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
withSectionSpacing(enabled?: boolean): this {
|
|
25
|
+
this.spacing = Boolean(enabled);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
invalidate(): void {
|
|
30
|
+
for (const component of this.sections()) {
|
|
31
|
+
component.invalidate?.();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render(width: number): string[] {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
|
|
38
|
+
for (const component of this.sections()) {
|
|
39
|
+
lines.push(...component.render(width));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private sections(): Component[] {
|
|
46
|
+
const sections: Component[] = [];
|
|
47
|
+
|
|
48
|
+
if (this.header) {
|
|
49
|
+
sections.push(this.header);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.spacing && this.header && this.body) {
|
|
53
|
+
sections.push(new Spacer(1));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.body) {
|
|
57
|
+
sections.push(this.body);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (this.spacing && (this.header || this.body) && this.footer) {
|
|
61
|
+
sections.push(new Spacer(1));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.footer) {
|
|
65
|
+
sections.push(this.footer);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return sections;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
import type { LogMatcherConfig } from "../../notifications/registry";
|
|
5
|
+
|
|
6
|
+
export function buildMatcherLine(
|
|
7
|
+
matcher: LogMatcherConfig,
|
|
8
|
+
theme: Theme,
|
|
9
|
+
prefix = "",
|
|
10
|
+
): Text {
|
|
11
|
+
const mode = matcher.mode ?? "literal";
|
|
12
|
+
const stream = matcher.stream ?? "both";
|
|
13
|
+
const repeat = matcher.repeat ? " (repeat)" : "";
|
|
14
|
+
const on = matcher.on ?? "turn";
|
|
15
|
+
const attentionTone =
|
|
16
|
+
on === "turn" ? "warning" : on === "context" ? "success" : "muted";
|
|
17
|
+
|
|
18
|
+
return new Text(
|
|
19
|
+
[
|
|
20
|
+
prefix,
|
|
21
|
+
theme.fg("muted", `[${stream}] ${mode}`),
|
|
22
|
+
" ",
|
|
23
|
+
theme.bold(theme.fg(attentionTone, `${on}${repeat}`)),
|
|
24
|
+
" ",
|
|
25
|
+
theme.fg("accent", formatPatternForDisplay(matcher.pattern)),
|
|
26
|
+
].join(""),
|
|
27
|
+
0,
|
|
28
|
+
0,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatPatternForDisplay(pattern: string): string {
|
|
33
|
+
let formatted = "";
|
|
34
|
+
|
|
35
|
+
for (const character of pattern) {
|
|
36
|
+
if (character === "\r") {
|
|
37
|
+
formatted += "\\r";
|
|
38
|
+
} else if (character === "\n") {
|
|
39
|
+
formatted += "\\n";
|
|
40
|
+
} else if (character === "\t") {
|
|
41
|
+
formatted += "\\t";
|
|
42
|
+
} else {
|
|
43
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
44
|
+
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) {
|
|
45
|
+
formatted += `\\x${codePoint.toString(16).padStart(2, "0")}`;
|
|
46
|
+
} else {
|
|
47
|
+
formatted += character;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return formatted;
|
|
53
|
+
}
|