@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,177 @@
|
|
|
1
|
+
import type { Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
export interface OverviewPanelOptions {
|
|
5
|
+
title?: string;
|
|
6
|
+
headerLeft?: string;
|
|
7
|
+
headerRight?: string;
|
|
8
|
+
body: Component;
|
|
9
|
+
footer?: Component;
|
|
10
|
+
borderStyle?: (text: string) => string;
|
|
11
|
+
titleStyle?: (text: string) => string;
|
|
12
|
+
metaStyle?: (text: string) => string;
|
|
13
|
+
padding?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const BORDER = {
|
|
17
|
+
tl: "╭",
|
|
18
|
+
tr: "╮",
|
|
19
|
+
bl: "╰",
|
|
20
|
+
br: "╯",
|
|
21
|
+
left: "│",
|
|
22
|
+
right: "│",
|
|
23
|
+
footSep: "├",
|
|
24
|
+
footEnd: "┤",
|
|
25
|
+
h: "─",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Local vendored variant of @aliou/pi-utils-ui Panel for /ps.
|
|
30
|
+
*
|
|
31
|
+
* The upstream Panel only supports a centered title. The overview needs
|
|
32
|
+
* left/center/right header slots. Keep this small and local so it can be
|
|
33
|
+
* upstreamed later without changing the rest of the /ps component.
|
|
34
|
+
*/
|
|
35
|
+
export class OverviewPanel implements Component {
|
|
36
|
+
private readonly title: string;
|
|
37
|
+
private readonly headerLeft: string;
|
|
38
|
+
private readonly headerRight: string;
|
|
39
|
+
private readonly body: Component;
|
|
40
|
+
private readonly footer?: Component;
|
|
41
|
+
private readonly borderStyle: (text: string) => string;
|
|
42
|
+
private readonly titleStyle: (text: string) => string;
|
|
43
|
+
private readonly metaStyle: (text: string) => string;
|
|
44
|
+
private readonly padding: number;
|
|
45
|
+
|
|
46
|
+
constructor(options: OverviewPanelOptions) {
|
|
47
|
+
this.title = options.title ?? "";
|
|
48
|
+
this.headerLeft = options.headerLeft ?? "";
|
|
49
|
+
this.headerRight = options.headerRight ?? "";
|
|
50
|
+
this.body = options.body;
|
|
51
|
+
this.footer = options.footer;
|
|
52
|
+
this.borderStyle = options.borderStyle ?? ((text) => text);
|
|
53
|
+
this.titleStyle = options.titleStyle ?? ((text) => text);
|
|
54
|
+
this.metaStyle = options.metaStyle ?? ((text) => text);
|
|
55
|
+
this.padding = options.padding ?? 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
render(width: number): string[] {
|
|
59
|
+
const lines: string[] = [];
|
|
60
|
+
const innerWidth = Math.max(0, width - 2);
|
|
61
|
+
const contentWidth = Math.max(0, innerWidth - this.padding * 2);
|
|
62
|
+
|
|
63
|
+
lines.push(this.renderHeader(innerWidth));
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < this.padding; i++) {
|
|
66
|
+
lines.push(this.borderLine(innerWidth));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const line of this.body.render(contentWidth)) {
|
|
70
|
+
lines.push(this.contentLine(innerWidth, line));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < this.padding; i++) {
|
|
74
|
+
lines.push(this.borderLine(innerWidth));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (this.footer) {
|
|
78
|
+
lines.push(
|
|
79
|
+
this.borderStyle(BORDER.footSep) +
|
|
80
|
+
this.borderStyle(BORDER.h.repeat(innerWidth)) +
|
|
81
|
+
this.borderStyle(BORDER.footEnd),
|
|
82
|
+
);
|
|
83
|
+
for (const line of this.footer.render(contentWidth)) {
|
|
84
|
+
lines.push(this.contentLine(innerWidth, line));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
lines.push(
|
|
89
|
+
this.borderStyle(BORDER.bl) +
|
|
90
|
+
this.borderStyle(BORDER.h.repeat(innerWidth)) +
|
|
91
|
+
this.borderStyle(BORDER.br),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
invalidate(): void {
|
|
98
|
+
this.body.invalidate();
|
|
99
|
+
this.footer?.invalidate();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private renderHeader(innerWidth: number): string {
|
|
103
|
+
if (innerWidth <= 0) {
|
|
104
|
+
return this.borderStyle(BORDER.tl) + this.borderStyle(BORDER.tr);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const leftRaw = this.headerLeft ? ` ${this.headerLeft} ` : "";
|
|
108
|
+
const rightRaw = this.headerRight ? ` ${this.headerRight} ` : "";
|
|
109
|
+
const centerRaw = this.title ? ` ${this.title} ` : "";
|
|
110
|
+
|
|
111
|
+
const center = this.titleStyle(truncateToWidth(centerRaw, innerWidth, ""));
|
|
112
|
+
const centerWidth = visibleWidth(center);
|
|
113
|
+
const centerStart = Math.max(0, Math.floor((innerWidth - centerWidth) / 2));
|
|
114
|
+
|
|
115
|
+
const leftBudget = centerStart;
|
|
116
|
+
const left = this.metaStyle(truncateToWidth(leftRaw, leftBudget, ""));
|
|
117
|
+
|
|
118
|
+
const centerEnd = centerStart + centerWidth;
|
|
119
|
+
const rightBudget = Math.max(0, innerWidth - centerEnd);
|
|
120
|
+
const right = this.metaStyle(truncateToWidth(rightRaw, rightBudget, ""));
|
|
121
|
+
const rightWidth = visibleWidth(right);
|
|
122
|
+
|
|
123
|
+
let output = "";
|
|
124
|
+
let cursor = 0;
|
|
125
|
+
const appendRuleTo = (target: number) => {
|
|
126
|
+
if (target <= cursor) return;
|
|
127
|
+
output += this.borderStyle(BORDER.h.repeat(target - cursor));
|
|
128
|
+
cursor = target;
|
|
129
|
+
};
|
|
130
|
+
const appendSegment = (segment: string) => {
|
|
131
|
+
output += segment;
|
|
132
|
+
cursor += visibleWidth(segment);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
appendSegment(left);
|
|
136
|
+
appendRuleTo(centerStart);
|
|
137
|
+
appendSegment(center);
|
|
138
|
+
if (rightWidth > 0) {
|
|
139
|
+
const rightStart = Math.max(0, innerWidth - rightWidth);
|
|
140
|
+
if (rightStart >= cursor) {
|
|
141
|
+
appendRuleTo(rightStart);
|
|
142
|
+
appendSegment(right);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
appendRuleTo(innerWidth);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
this.borderStyle(BORDER.tl) +
|
|
149
|
+
truncateToWidth(output, innerWidth, "", true) +
|
|
150
|
+
this.borderStyle(BORDER.tr)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private borderLine(innerWidth: number): string {
|
|
155
|
+
return (
|
|
156
|
+
this.borderStyle(BORDER.left) +
|
|
157
|
+
" ".repeat(innerWidth) +
|
|
158
|
+
this.borderStyle(BORDER.right)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private contentLine(innerWidth: number, content: string): string {
|
|
163
|
+
const padded =
|
|
164
|
+
" ".repeat(this.padding) +
|
|
165
|
+
truncateToWidth(
|
|
166
|
+
content,
|
|
167
|
+
Math.max(0, innerWidth - this.padding * 2),
|
|
168
|
+
"",
|
|
169
|
+
true,
|
|
170
|
+
) +
|
|
171
|
+
" ".repeat(this.padding);
|
|
172
|
+
const inner = truncateToWidth(padded, innerWidth, "", true);
|
|
173
|
+
return (
|
|
174
|
+
this.borderStyle(BORDER.left) + inner + this.borderStyle(BORDER.right)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProcessProtocolConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONFIG: ProcessProtocolConfig = {
|
|
4
|
+
execution: {
|
|
5
|
+
shellPath: undefined,
|
|
6
|
+
},
|
|
7
|
+
interception: {
|
|
8
|
+
blockBackgroundCommands: false,
|
|
9
|
+
},
|
|
10
|
+
processList: {
|
|
11
|
+
maxPreviewLines: 12,
|
|
12
|
+
maxVisibleProcesses: 8,
|
|
13
|
+
},
|
|
14
|
+
output: {
|
|
15
|
+
defaultTailLines: 100,
|
|
16
|
+
maxOutputLines: 200,
|
|
17
|
+
maxOutputBytes: 4 * 1024 * 1024,
|
|
18
|
+
},
|
|
19
|
+
follow: {
|
|
20
|
+
enabledByDefault: true,
|
|
21
|
+
autoHideOnFinish: true,
|
|
22
|
+
},
|
|
23
|
+
widget: {
|
|
24
|
+
showStatusWidget: false,
|
|
25
|
+
dockDefaultState: "closed",
|
|
26
|
+
dockHeight: 12,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { DEFAULT_CONFIG } from "./defaults";
|
|
2
|
+
export {
|
|
3
|
+
configLoader,
|
|
4
|
+
createSettingsConfigStore,
|
|
5
|
+
drainImportMessages,
|
|
6
|
+
loadProcessConfig,
|
|
7
|
+
} from "./loader";
|
|
8
|
+
export { migrations } from "./migrations";
|
|
9
|
+
export {
|
|
10
|
+
PROCESS_CONFIG_SCHEMA_URL,
|
|
11
|
+
PROCESS_CONFIG_SCHEMA_VERSION,
|
|
12
|
+
} from "./schema";
|
|
13
|
+
export type { ProcessConfig, ProcessProtocolConfig } from "./types";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigLoader,
|
|
3
|
+
type ConfigStore,
|
|
4
|
+
type Scope,
|
|
5
|
+
} from "@aliou/pi-utils-settings";
|
|
6
|
+
import { DEFAULT_CONFIG } from "./defaults";
|
|
7
|
+
import { importLegacyProcessConfig, migrations } from "./migrations";
|
|
8
|
+
import { PROCESS_CONFIG_SCHEMA_URL } from "./schema";
|
|
9
|
+
import type { ProcessConfig, ProcessProtocolConfig } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Pending messages from the one-time legacy config import. Drained alongside
|
|
13
|
+
* ConfigLoader migration messages in registerMigrationMessageNotifications.
|
|
14
|
+
*/
|
|
15
|
+
const importMessages: string[] = [];
|
|
16
|
+
|
|
17
|
+
export async function loadProcessConfig(): Promise<void> {
|
|
18
|
+
const result = await importLegacyProcessConfig();
|
|
19
|
+
if (result.kind === "imported") {
|
|
20
|
+
importMessages.push(
|
|
21
|
+
`Imported pi-processes settings from the legacy 0.9.4 config file at ${result.legacyPath}. Your existing settings were preserved and migrated to the current schema.`,
|
|
22
|
+
);
|
|
23
|
+
} else if (result.kind === "invalid") {
|
|
24
|
+
importMessages.push(
|
|
25
|
+
`Found a legacy 0.9.4 config file at ${result.legacyPath}, but it is not a readable JSON object (${result.error}). It was left in place and the current settings were loaded from defaults instead.`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
await configLoader.load();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Drain (remove and return) all pending legacy-import messages. */
|
|
32
|
+
export function drainImportMessages(): string[] {
|
|
33
|
+
return importMessages.splice(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const configLoader = new ConfigLoader<
|
|
37
|
+
ProcessConfig,
|
|
38
|
+
ProcessProtocolConfig
|
|
39
|
+
>("processes", DEFAULT_CONFIG, {
|
|
40
|
+
scopes: ["global", "local", "memory"],
|
|
41
|
+
migrations,
|
|
42
|
+
schemaUrl: PROCESS_CONFIG_SCHEMA_URL,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export function createSettingsConfigStore(): ConfigStore<
|
|
46
|
+
ProcessConfig,
|
|
47
|
+
ProcessProtocolConfig
|
|
48
|
+
> {
|
|
49
|
+
return {
|
|
50
|
+
save: (scope, config) => configLoader.save(scope, config),
|
|
51
|
+
getConfig: () => configLoader.getConfig(),
|
|
52
|
+
getRawConfig: (scope) => configLoader.getRawConfig(scope),
|
|
53
|
+
hasScope: (scope) => configLoader.hasScope(scope),
|
|
54
|
+
hasConfig: (scope) => configLoader.hasConfig(scope),
|
|
55
|
+
getEnabledScopes: () => {
|
|
56
|
+
const enabled = new Set(configLoader.getEnabledScopes());
|
|
57
|
+
return (["global", "local", "memory"] as Scope[]).filter((scope) =>
|
|
58
|
+
enabled.has(scope),
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { constants, copyFile, readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
const { COPYFILE_EXCL } = constants;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Config namespace used by pi-processes before 0.10.0.
|
|
10
|
+
*
|
|
11
|
+
* 0.9.4 shipped as the "process" extension, so its settings lived at
|
|
12
|
+
* `~/.pi/agent/extensions/process.json`. The rewrite renamed the namespace
|
|
13
|
+
* to "processes" (`processes.json`). ConfigLoader derives the file path
|
|
14
|
+
* strictly from the extension name, with no alias or legacy support.
|
|
15
|
+
*/
|
|
16
|
+
const LEGACY_EXTENSION_NAME = "process";
|
|
17
|
+
const EXTENSION_NAME = "processes";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Outcome of {@link importLegacyProcessConfig}.
|
|
21
|
+
*
|
|
22
|
+
* - `imported`: legacy file was copied to the current path. ConfigLoader should
|
|
23
|
+
* load the new file and run the `001` schema migration on real 0.9.4 data.
|
|
24
|
+
* - `skipped`: nothing to import (no legacy file, or current file already
|
|
25
|
+
* exists). ConfigLoader proceeds normally.
|
|
26
|
+
* - `invalid`: the legacy file exists but is not a readable JSON object. The
|
|
27
|
+
* copy is **not** performed, so ConfigLoader falls back to defaults instead
|
|
28
|
+
* of inheriting a corrupt config. The caller can surface a warning.
|
|
29
|
+
*/
|
|
30
|
+
export type LegacyImportResult =
|
|
31
|
+
| { kind: "imported"; legacyPath: string }
|
|
32
|
+
| { kind: "skipped" }
|
|
33
|
+
| { kind: "invalid"; legacyPath: string; error: string };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* One-time import of the legacy 0.9.4 config file.
|
|
37
|
+
*
|
|
38
|
+
* Before `ConfigLoader.load()` runs, copy the legacy `process.json` to the
|
|
39
|
+
* new `processes.json` if (and only if) the new file does not exist yet. The
|
|
40
|
+
* subsequent `001` schema migration then runs on the real 0.9.4 data instead
|
|
41
|
+
* of defaults.
|
|
42
|
+
*
|
|
43
|
+
* This cannot be a `Migration` array entry: `ConfigLoader.load()` only runs
|
|
44
|
+
* migrations after a file is successfully read, and returns `null` for a
|
|
45
|
+
* missing file. So a migration would never execute for the exact users it
|
|
46
|
+
* targets (those who still only have `process.json`). The pre-load step
|
|
47
|
+
* closes that gap.
|
|
48
|
+
*
|
|
49
|
+
* The legacy file is validated as a JSON object **before** copying. A corrupt
|
|
50
|
+
* `process.json` is left untouched; importing it would make ConfigLoader's
|
|
51
|
+
* `readFile` throw and silently fall back to defaults while a "settings
|
|
52
|
+
* preserved" message is shown -- which would be actively misleading.
|
|
53
|
+
*
|
|
54
|
+
* The copy uses `COPYFILE_EXCL` so a concurrent import (e.g. two Pi instances
|
|
55
|
+
* running in the same agent dir during an upgrade) cannot overwrite a
|
|
56
|
+
* `processes.json` the other instance just created. The loser treats that as
|
|
57
|
+
* "skipped".
|
|
58
|
+
*
|
|
59
|
+
* The legacy file is left in place as an accidental backup; it is ignored on
|
|
60
|
+
* subsequent loads because `processes.json` now exists.
|
|
61
|
+
*
|
|
62
|
+
* 0.9.4 only used the global scope (`~/.pi/agent/extensions/`), so only the
|
|
63
|
+
* global path is imported. No local-scope import is needed.
|
|
64
|
+
*/
|
|
65
|
+
export async function importLegacyProcessConfig(): Promise<LegacyImportResult> {
|
|
66
|
+
const agentDir = getAgentDir();
|
|
67
|
+
const legacyPath = resolve(
|
|
68
|
+
agentDir,
|
|
69
|
+
`extensions/${LEGACY_EXTENSION_NAME}.json`,
|
|
70
|
+
);
|
|
71
|
+
const currentPath = resolve(agentDir, `extensions/${EXTENSION_NAME}.json`);
|
|
72
|
+
|
|
73
|
+
if (!existsSync(legacyPath)) return { kind: "skipped" };
|
|
74
|
+
if (existsSync(currentPath)) return { kind: "skipped" };
|
|
75
|
+
|
|
76
|
+
// Validate the legacy file before copying. A corrupt process.json would
|
|
77
|
+
// make ConfigLoader.readFile throw and fall back to defaults; importing it
|
|
78
|
+
// anyway would let us show a misleading "settings preserved" message.
|
|
79
|
+
try {
|
|
80
|
+
const raw = await readFile(legacyPath, "utf-8");
|
|
81
|
+
const parsed: unknown = JSON.parse(raw);
|
|
82
|
+
if (
|
|
83
|
+
parsed === null ||
|
|
84
|
+
typeof parsed !== "object" ||
|
|
85
|
+
Array.isArray(parsed)
|
|
86
|
+
) {
|
|
87
|
+
return {
|
|
88
|
+
kind: "invalid",
|
|
89
|
+
legacyPath,
|
|
90
|
+
error: "legacy config is not a JSON object",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
kind: "invalid",
|
|
96
|
+
legacyPath,
|
|
97
|
+
error: (error as Error).message,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// COPYFILE_EXCL makes the copy fail (EEXIST) if another instance created
|
|
102
|
+
// processes.json between our existence check and the copy. Treat that as a
|
|
103
|
+
// benign skip rather than a data-loss overwrite.
|
|
104
|
+
try {
|
|
105
|
+
await copyFile(legacyPath, currentPath, COPYFILE_EXCL);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const err = error as NodeJS.ErrnoException;
|
|
108
|
+
if (err.code === "EEXIST") return { kind: "skipped" };
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { kind: "imported", legacyPath };
|
|
113
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { Migration } from "@aliou/pi-utils-settings";
|
|
2
|
+
|
|
3
|
+
import { PROCESS_CONFIG_SCHEMA_URL } from "../schema";
|
|
4
|
+
import type { ProcessConfig } from "../types";
|
|
5
|
+
|
|
6
|
+
type DockDefaultStateV094 = "hidden" | "collapsed";
|
|
7
|
+
type DockDefaultStateV0100 = "closed" | "collapsed" | "expanded";
|
|
8
|
+
|
|
9
|
+
export interface ConfigV094 {
|
|
10
|
+
$schema?: string;
|
|
11
|
+
execution?: {
|
|
12
|
+
shellPath?: string;
|
|
13
|
+
};
|
|
14
|
+
interception?: {
|
|
15
|
+
blockBackgroundCommands?: boolean;
|
|
16
|
+
};
|
|
17
|
+
processList?: {
|
|
18
|
+
maxPreviewLines?: number;
|
|
19
|
+
maxVisibleProcesses?: number;
|
|
20
|
+
};
|
|
21
|
+
output?: {
|
|
22
|
+
defaultTailLines?: number;
|
|
23
|
+
maxOutputLines?: number;
|
|
24
|
+
};
|
|
25
|
+
follow?: {
|
|
26
|
+
enabledByDefault?: boolean;
|
|
27
|
+
autoHideOnFinish?: boolean;
|
|
28
|
+
};
|
|
29
|
+
widget?: {
|
|
30
|
+
showStatusWidget?: boolean;
|
|
31
|
+
dockDefaultState?: DockDefaultStateV094;
|
|
32
|
+
dockHeight?: number;
|
|
33
|
+
};
|
|
34
|
+
keybindings?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ConfigV0100 {
|
|
38
|
+
$schema?: string;
|
|
39
|
+
execution?: {
|
|
40
|
+
shellPath?: string;
|
|
41
|
+
};
|
|
42
|
+
interception?: {
|
|
43
|
+
blockBackgroundCommands?: boolean;
|
|
44
|
+
};
|
|
45
|
+
processList?: {
|
|
46
|
+
maxPreviewLines?: number;
|
|
47
|
+
maxVisibleProcesses?: number;
|
|
48
|
+
};
|
|
49
|
+
output?: {
|
|
50
|
+
defaultTailLines?: number;
|
|
51
|
+
maxOutputLines?: number;
|
|
52
|
+
};
|
|
53
|
+
follow?: {
|
|
54
|
+
enabledByDefault?: boolean;
|
|
55
|
+
autoHideOnFinish?: boolean;
|
|
56
|
+
};
|
|
57
|
+
widget?: {
|
|
58
|
+
showStatusWidget?: boolean;
|
|
59
|
+
dockDefaultState?: DockDefaultStateV0100;
|
|
60
|
+
dockHeight?: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Config v0.9.4 -> v0.10.0 settings migration.
|
|
66
|
+
*
|
|
67
|
+
* Current sections with the same shape are copied through:
|
|
68
|
+
* execution, interception, processList, output, and follow.
|
|
69
|
+
*
|
|
70
|
+
* Widget settings changed:
|
|
71
|
+
* - widget.dockDefaultState "hidden" became "closed".
|
|
72
|
+
* - widget.dockHeight keeps the same key but means dock log rows.
|
|
73
|
+
* - widget.showStatusWidget is preserved (still controls the status widget).
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
77
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function setIfDefined<T extends object, K extends keyof T>(
|
|
81
|
+
target: T,
|
|
82
|
+
key: K,
|
|
83
|
+
value: T[K] | undefined,
|
|
84
|
+
): void {
|
|
85
|
+
if (value !== undefined) target[key] = value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function migrateDockDefaultState(
|
|
89
|
+
value: unknown,
|
|
90
|
+
): DockDefaultStateV0100 | undefined {
|
|
91
|
+
if (value === "hidden") return "closed";
|
|
92
|
+
if (value === "closed" || value === "collapsed" || value === "expanded") {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function needsConfigV094ToV0100Migration(
|
|
99
|
+
config: ProcessConfig,
|
|
100
|
+
): boolean {
|
|
101
|
+
const root = config as Record<string, unknown>;
|
|
102
|
+
const widget = isRecord(root.widget) ? root.widget : undefined;
|
|
103
|
+
|
|
104
|
+
return widget?.dockDefaultState === "hidden";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function migrateConfigV094ToV0100(config: ConfigV094): ConfigV0100 {
|
|
108
|
+
const next: ConfigV0100 = { $schema: PROCESS_CONFIG_SCHEMA_URL };
|
|
109
|
+
|
|
110
|
+
if (config.execution) {
|
|
111
|
+
const execution: NonNullable<ConfigV0100["execution"]> = {};
|
|
112
|
+
setIfDefined(execution, "shellPath", config.execution.shellPath);
|
|
113
|
+
setNonEmpty(next, "execution", execution);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (config.interception) {
|
|
117
|
+
const interception: NonNullable<ConfigV0100["interception"]> = {};
|
|
118
|
+
setIfDefined(
|
|
119
|
+
interception,
|
|
120
|
+
"blockBackgroundCommands",
|
|
121
|
+
config.interception.blockBackgroundCommands,
|
|
122
|
+
);
|
|
123
|
+
setNonEmpty(next, "interception", interception);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (config.processList) {
|
|
127
|
+
const processList: NonNullable<ConfigV0100["processList"]> = {};
|
|
128
|
+
setIfDefined(
|
|
129
|
+
processList,
|
|
130
|
+
"maxVisibleProcesses",
|
|
131
|
+
config.processList.maxVisibleProcesses,
|
|
132
|
+
);
|
|
133
|
+
setIfDefined(
|
|
134
|
+
processList,
|
|
135
|
+
"maxPreviewLines",
|
|
136
|
+
config.processList.maxPreviewLines,
|
|
137
|
+
);
|
|
138
|
+
setNonEmpty(next, "processList", processList);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (config.output) {
|
|
142
|
+
const output: NonNullable<ConfigV0100["output"]> = {};
|
|
143
|
+
setIfDefined(output, "defaultTailLines", config.output.defaultTailLines);
|
|
144
|
+
setIfDefined(output, "maxOutputLines", config.output.maxOutputLines);
|
|
145
|
+
setNonEmpty(next, "output", output);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (config.follow) {
|
|
149
|
+
const follow: NonNullable<ConfigV0100["follow"]> = {};
|
|
150
|
+
setIfDefined(follow, "enabledByDefault", config.follow.enabledByDefault);
|
|
151
|
+
setIfDefined(follow, "autoHideOnFinish", config.follow.autoHideOnFinish);
|
|
152
|
+
setNonEmpty(next, "follow", follow);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (config.widget) {
|
|
156
|
+
const widget: NonNullable<ConfigV0100["widget"]> = {};
|
|
157
|
+
setIfDefined(widget, "showStatusWidget", config.widget.showStatusWidget);
|
|
158
|
+
setIfDefined(
|
|
159
|
+
widget,
|
|
160
|
+
"dockDefaultState",
|
|
161
|
+
migrateDockDefaultState(config.widget.dockDefaultState),
|
|
162
|
+
);
|
|
163
|
+
setIfDefined(widget, "dockHeight", config.widget.dockHeight);
|
|
164
|
+
setNonEmpty(next, "widget", widget);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return next;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setNonEmpty<K extends keyof ConfigV0100>(
|
|
171
|
+
target: ConfigV0100,
|
|
172
|
+
key: K,
|
|
173
|
+
value: NonNullable<ConfigV0100[K]>,
|
|
174
|
+
): void {
|
|
175
|
+
if (Object.keys(value).length > 0) target[key] = value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const configV094ToV0100Migration: Migration<ProcessConfig> = {
|
|
179
|
+
name: "001-v0-9-4-to-v0-10-0-config",
|
|
180
|
+
shouldRun: (config) => needsConfigV094ToV0100Migration(config),
|
|
181
|
+
run: (config) => migrateConfigV094ToV0100(config as ConfigV094),
|
|
182
|
+
message:
|
|
183
|
+
"Migrated pi-processes settings to the current schema. Mapped the dock hidden state to closed.",
|
|
184
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Migration } from "@aliou/pi-utils-settings";
|
|
2
|
+
|
|
3
|
+
import type { ProcessConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export const PROCESS_CONFIG_VERSION = "0.10.0";
|
|
6
|
+
|
|
7
|
+
export function needsConfigVersionStamp(config: ProcessConfig): boolean {
|
|
8
|
+
return (config.version ?? "") < PROCESS_CONFIG_VERSION;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function stampConfigVersion(config: ProcessConfig): ProcessConfig {
|
|
12
|
+
return { ...config, version: PROCESS_CONFIG_VERSION };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const configVersionStampMigration: Migration<ProcessConfig> = {
|
|
16
|
+
name: "002-stamp-v0-10-0-config-version",
|
|
17
|
+
shouldRun: needsConfigVersionStamp,
|
|
18
|
+
run: (config) => stampConfigVersion(config),
|
|
19
|
+
message:
|
|
20
|
+
"Updated pi-processes settings for v0.10.0. This release rewrites the package into separate process, logs, and dock extensions while preserving your existing settings. Release notes will be published at https://github.com/aliou/pi-processes/releases/tag/v0.10.0.",
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Migration } from "@aliou/pi-utils-settings";
|
|
2
|
+
|
|
3
|
+
import type { ProcessConfig } from "../types";
|
|
4
|
+
import { configV094ToV0100Migration } from "./001-v0-9-4-to-v0-10-0-config";
|
|
5
|
+
import { configVersionStampMigration } from "./002-stamp-v0-10-0-config-version";
|
|
6
|
+
|
|
7
|
+
export { importLegacyProcessConfig } from "./000-import-legacy-process-config";
|
|
8
|
+
export {
|
|
9
|
+
type ConfigV094,
|
|
10
|
+
type ConfigV0100,
|
|
11
|
+
configV094ToV0100Migration,
|
|
12
|
+
migrateConfigV094ToV0100,
|
|
13
|
+
needsConfigV094ToV0100Migration,
|
|
14
|
+
} from "./001-v0-9-4-to-v0-10-0-config";
|
|
15
|
+
export {
|
|
16
|
+
configVersionStampMigration,
|
|
17
|
+
needsConfigVersionStamp,
|
|
18
|
+
PROCESS_CONFIG_VERSION,
|
|
19
|
+
stampConfigVersion,
|
|
20
|
+
} from "./002-stamp-v0-10-0-config-version";
|
|
21
|
+
|
|
22
|
+
export const migrations: Migration<ProcessConfig>[] = [
|
|
23
|
+
configV094ToV0100Migration,
|
|
24
|
+
configVersionStampMigration,
|
|
25
|
+
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { buildSchemaUrl } from "@aliou/pi-utils-settings";
|
|
2
|
+
|
|
3
|
+
import pkg from "../../../package.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
export const PROCESS_CONFIG_SCHEMA_VERSION = pkg.version;
|
|
6
|
+
export const PROCESS_CONFIG_SCHEMA_URL = buildSchemaUrl(
|
|
7
|
+
pkg.name,
|
|
8
|
+
PROCESS_CONFIG_SCHEMA_VERSION,
|
|
9
|
+
);
|