@aliou/pi-processes 0.1.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/README.md +63 -0
- package/commands/index.ts +488 -0
- package/constants.ts +2 -0
- package/hooks/cleanup.ts +8 -0
- package/hooks/index.ts +16 -0
- package/hooks/message-renderer.ts +83 -0
- package/hooks/process-end.ts +103 -0
- package/hooks/widget.ts +144 -0
- package/index.ts +13 -0
- package/manager.ts +405 -0
- package/package.json +35 -0
- package/tools/index.ts +532 -0
package/tools/index.ts
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolResult,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
Theme,
|
|
7
|
+
ToolRenderResultOptions,
|
|
8
|
+
} from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
10
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
11
|
+
import type { ProcessInfo, ProcessManager } from "../manager";
|
|
12
|
+
|
|
13
|
+
const ProcessesParams = Type.Object({
|
|
14
|
+
action: StringEnum(
|
|
15
|
+
["start", "list", "output", "logs", "kill", "clear"] as const,
|
|
16
|
+
{
|
|
17
|
+
description:
|
|
18
|
+
"Action: start (run command), list (show all), output (get recent output), logs (get log file paths), kill (terminate), clear (remove finished)",
|
|
19
|
+
},
|
|
20
|
+
),
|
|
21
|
+
command: Type.Optional(
|
|
22
|
+
Type.String({ description: "Command to run (required for start)" }),
|
|
23
|
+
),
|
|
24
|
+
name: Type.Optional(
|
|
25
|
+
Type.String({
|
|
26
|
+
description:
|
|
27
|
+
"Friendly name for the process (optional for start, e.g. 'backend-dev', 'test-runner')",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
id: Type.Optional(
|
|
31
|
+
Type.String({
|
|
32
|
+
description:
|
|
33
|
+
"Process ID or name to match (required for output/kill/logs). Can be proc_N or friendly name.",
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
notifyOnSuccess: Type.Optional(
|
|
37
|
+
Type.Boolean({
|
|
38
|
+
description:
|
|
39
|
+
"Notify when process completes successfully (default: false). Use for builds/tests where you need confirmation.",
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
notifyOnFailure: Type.Optional(
|
|
43
|
+
Type.Boolean({
|
|
44
|
+
description:
|
|
45
|
+
"Notify when process fails/crashes (default: true). Use to be alerted of unexpected failures.",
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
notifyOnKill: Type.Optional(
|
|
49
|
+
Type.Boolean({
|
|
50
|
+
description:
|
|
51
|
+
"Notify when process is killed by external signal (default: false). Note: killing via tool never notifies.",
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
type ProcessesParamsType = Static<typeof ProcessesParams>;
|
|
57
|
+
|
|
58
|
+
interface ProcessesDetails {
|
|
59
|
+
action: string;
|
|
60
|
+
success: boolean;
|
|
61
|
+
message: string;
|
|
62
|
+
process?: ProcessInfo;
|
|
63
|
+
processes?: ProcessInfo[];
|
|
64
|
+
output?: { stdout: string[]; stderr: string[]; status: string };
|
|
65
|
+
logFiles?: { stdoutFile: string; stderrFile: string };
|
|
66
|
+
cleared?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface ExecuteResult {
|
|
70
|
+
content: Array<{ type: "text"; text: string }>;
|
|
71
|
+
details: ProcessesDetails;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatRuntime(startTime: number, endTime: number | null): string {
|
|
75
|
+
const end = endTime ?? Date.now();
|
|
76
|
+
const ms = end - startTime;
|
|
77
|
+
const seconds = Math.floor(ms / 1000);
|
|
78
|
+
const minutes = Math.floor(seconds / 60);
|
|
79
|
+
const hours = Math.floor(minutes / 60);
|
|
80
|
+
|
|
81
|
+
if (hours > 0) {
|
|
82
|
+
return `${hours}h ${minutes % 60}m`;
|
|
83
|
+
}
|
|
84
|
+
if (minutes > 0) {
|
|
85
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
86
|
+
}
|
|
87
|
+
return `${seconds}s`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatStatus(proc: ProcessInfo): string {
|
|
91
|
+
if (proc.status === "running") return "running";
|
|
92
|
+
if (proc.status === "killed") return "killed";
|
|
93
|
+
if (proc.success) return "exited(0)";
|
|
94
|
+
return `exited(${proc.exitCode ?? "?"})`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function truncateCmd(cmd: string, max = 40): string {
|
|
98
|
+
if (cmd.length <= max) return cmd;
|
|
99
|
+
return `${cmd.slice(0, max - 3)}...`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function setupProcessesTools(pi: ExtensionAPI, manager: ProcessManager) {
|
|
103
|
+
pi.registerTool<typeof ProcessesParams, ProcessesDetails>({
|
|
104
|
+
name: "processes",
|
|
105
|
+
label: "Processes",
|
|
106
|
+
description: `Manage background processes. Actions:
|
|
107
|
+
- start: Run command in background (requires 'command', optional 'name' for friendly display name)
|
|
108
|
+
- notifyOnSuccess (default: false): Get notified when process completes successfully
|
|
109
|
+
- notifyOnFailure (default: true): Get notified when process crashes/fails
|
|
110
|
+
- notifyOnKill (default: false): Get notified if killed by external signal (killing via tool never notifies)
|
|
111
|
+
- list: Show all managed processes with their IDs and names
|
|
112
|
+
- output: Get recent stdout/stderr (requires 'id' - can be proc_N or name match)
|
|
113
|
+
- logs: Get log file paths to inspect with read tool (requires 'id')
|
|
114
|
+
- kill: Terminate a process (requires 'id' - can be proc_N or name match like "backend")
|
|
115
|
+
- clear: Remove all finished processes from the list
|
|
116
|
+
|
|
117
|
+
Important: You DON'T need to poll or wait for processes. Notifications arrive automatically based on your preferences. Start processes and continue with other work - you'll be informed if something requires attention.
|
|
118
|
+
|
|
119
|
+
Note: User always sees notifications in UI. Notification preferences only control whether YOU (the agent) are informed.`,
|
|
120
|
+
|
|
121
|
+
parameters: ProcessesParams,
|
|
122
|
+
|
|
123
|
+
async execute(
|
|
124
|
+
_toolCallId: string,
|
|
125
|
+
params: ProcessesParamsType,
|
|
126
|
+
_onUpdate: unknown,
|
|
127
|
+
ctx: ExtensionContext,
|
|
128
|
+
_signal?: AbortSignal,
|
|
129
|
+
): Promise<ExecuteResult> {
|
|
130
|
+
switch (params.action) {
|
|
131
|
+
case "start": {
|
|
132
|
+
if (!params.command) {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{ type: "text", text: "Missing required parameter: command" },
|
|
136
|
+
],
|
|
137
|
+
details: {
|
|
138
|
+
action: "start",
|
|
139
|
+
success: false,
|
|
140
|
+
message: "Missing required parameter: command",
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const proc = manager.start(params.command, ctx.cwd, params.name, {
|
|
145
|
+
notifyOnSuccess: params.notifyOnSuccess,
|
|
146
|
+
notifyOnFailure: params.notifyOnFailure,
|
|
147
|
+
notifyOnKill: params.notifyOnKill,
|
|
148
|
+
});
|
|
149
|
+
const message = `Started "${proc.name}" (${proc.id}, PID: ${proc.pid})\nLogs: ${proc.stdoutFile}`;
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: message }],
|
|
152
|
+
details: {
|
|
153
|
+
action: "start",
|
|
154
|
+
success: true,
|
|
155
|
+
message,
|
|
156
|
+
process: proc,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case "list": {
|
|
162
|
+
const processes = manager.list();
|
|
163
|
+
if (processes.length === 0) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{ type: "text", text: "No background processes running" },
|
|
167
|
+
],
|
|
168
|
+
details: {
|
|
169
|
+
action: "list",
|
|
170
|
+
success: true,
|
|
171
|
+
message: "No background processes running",
|
|
172
|
+
processes: [],
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const summary = processes
|
|
177
|
+
.map(
|
|
178
|
+
(p) =>
|
|
179
|
+
`${p.id} "${p.name}": ${truncateCmd(p.command)} [${formatStatus(p)}] ${formatRuntime(p.startTime, p.endTime)}`,
|
|
180
|
+
)
|
|
181
|
+
.join("\n");
|
|
182
|
+
const message = `${processes.length} process(es):\n${summary}`;
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: message }],
|
|
185
|
+
details: {
|
|
186
|
+
action: "list",
|
|
187
|
+
success: true,
|
|
188
|
+
message,
|
|
189
|
+
processes,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
case "output": {
|
|
195
|
+
if (!params.id) {
|
|
196
|
+
return {
|
|
197
|
+
content: [
|
|
198
|
+
{ type: "text", text: "Missing required parameter: id" },
|
|
199
|
+
],
|
|
200
|
+
details: {
|
|
201
|
+
action: "output",
|
|
202
|
+
success: false,
|
|
203
|
+
message: "Missing required parameter: id",
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const proc = manager.find(params.id);
|
|
208
|
+
if (!proc) {
|
|
209
|
+
const message = `Process not found: ${params.id}`;
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: message }],
|
|
212
|
+
details: {
|
|
213
|
+
action: "output",
|
|
214
|
+
success: false,
|
|
215
|
+
message,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const output = manager.getOutput(proc.id);
|
|
220
|
+
if (!output) {
|
|
221
|
+
const message = `Could not read output for: ${proc.id}`;
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: message }],
|
|
224
|
+
details: {
|
|
225
|
+
action: "output",
|
|
226
|
+
success: false,
|
|
227
|
+
message,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const stdoutLines = output.stdout.length;
|
|
232
|
+
const stderrLines = output.stderr.length;
|
|
233
|
+
const message = `"${proc.name}" (${proc.id}) [${formatStatus(proc)}]: ${stdoutLines} stdout lines, ${stderrLines} stderr lines`;
|
|
234
|
+
|
|
235
|
+
const outputParts: string[] = [message];
|
|
236
|
+
if (output.stdout.length > 0) {
|
|
237
|
+
outputParts.push("\n--- stdout (last 100 lines) ---");
|
|
238
|
+
outputParts.push(...output.stdout.slice(-100));
|
|
239
|
+
}
|
|
240
|
+
if (output.stderr.length > 0) {
|
|
241
|
+
outputParts.push("\n--- stderr (last 100 lines) ---");
|
|
242
|
+
outputParts.push(...output.stderr.slice(-100));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: "text", text: outputParts.join("\n") }],
|
|
247
|
+
details: {
|
|
248
|
+
action: "output",
|
|
249
|
+
success: true,
|
|
250
|
+
message,
|
|
251
|
+
output,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case "logs": {
|
|
257
|
+
if (!params.id) {
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{ type: "text", text: "Missing required parameter: id" },
|
|
261
|
+
],
|
|
262
|
+
details: {
|
|
263
|
+
action: "logs",
|
|
264
|
+
success: false,
|
|
265
|
+
message: "Missing required parameter: id",
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const proc = manager.find(params.id);
|
|
270
|
+
if (!proc) {
|
|
271
|
+
const message = `Process not found: ${params.id}`;
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: message }],
|
|
274
|
+
details: {
|
|
275
|
+
action: "logs",
|
|
276
|
+
success: false,
|
|
277
|
+
message,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const logFiles = manager.getLogFiles(proc.id);
|
|
282
|
+
if (!logFiles) {
|
|
283
|
+
const message = `Could not get log files for: ${proc.id}`;
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: message }],
|
|
286
|
+
details: {
|
|
287
|
+
action: "logs",
|
|
288
|
+
success: false,
|
|
289
|
+
message,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const message = `Log files for "${proc.name}" (${proc.id}):\n stdout: ${logFiles.stdoutFile}\n stderr: ${logFiles.stderrFile}\n\nUse the read tool to inspect these files.`;
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: "text", text: message }],
|
|
296
|
+
details: {
|
|
297
|
+
action: "logs",
|
|
298
|
+
success: true,
|
|
299
|
+
message,
|
|
300
|
+
logFiles,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
case "kill": {
|
|
306
|
+
if (!params.id) {
|
|
307
|
+
return {
|
|
308
|
+
content: [
|
|
309
|
+
{ type: "text", text: "Missing required parameter: id" },
|
|
310
|
+
],
|
|
311
|
+
details: {
|
|
312
|
+
action: "kill",
|
|
313
|
+
success: false,
|
|
314
|
+
message: "Missing required parameter: id",
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const proc = manager.find(params.id);
|
|
319
|
+
if (!proc) {
|
|
320
|
+
const message = `Process not found: ${params.id}`;
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: message }],
|
|
323
|
+
details: {
|
|
324
|
+
action: "kill",
|
|
325
|
+
success: false,
|
|
326
|
+
message,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const killed = manager.kill(proc.id);
|
|
331
|
+
if (killed) {
|
|
332
|
+
const message = `Killed "${proc.name}" (${proc.id})`;
|
|
333
|
+
return {
|
|
334
|
+
content: [{ type: "text", text: message }],
|
|
335
|
+
details: {
|
|
336
|
+
action: "kill",
|
|
337
|
+
success: true,
|
|
338
|
+
message,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const message = `Failed to kill "${proc.name}" (${proc.id})`;
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: message }],
|
|
345
|
+
details: {
|
|
346
|
+
action: "kill",
|
|
347
|
+
success: false,
|
|
348
|
+
message,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case "clear": {
|
|
354
|
+
const cleared = manager.clearFinished();
|
|
355
|
+
const message =
|
|
356
|
+
cleared > 0
|
|
357
|
+
? `Cleared ${cleared} finished process(es)`
|
|
358
|
+
: "No finished processes to clear";
|
|
359
|
+
return {
|
|
360
|
+
content: [{ type: "text", text: message }],
|
|
361
|
+
details: {
|
|
362
|
+
action: "clear",
|
|
363
|
+
success: true,
|
|
364
|
+
message,
|
|
365
|
+
cleared,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
default:
|
|
371
|
+
return {
|
|
372
|
+
content: [
|
|
373
|
+
{ type: "text", text: `Unknown action: ${params.action}` },
|
|
374
|
+
],
|
|
375
|
+
details: {
|
|
376
|
+
action: params.action,
|
|
377
|
+
success: false,
|
|
378
|
+
message: `Unknown action: ${params.action}`,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
renderCall(args: ProcessesParamsType, theme: Theme): Text {
|
|
385
|
+
let text = theme.fg("toolTitle", theme.bold("processes "));
|
|
386
|
+
text += theme.fg("accent", args.action);
|
|
387
|
+
|
|
388
|
+
switch (args.action) {
|
|
389
|
+
case "start":
|
|
390
|
+
if (args.name) {
|
|
391
|
+
text += ` ${theme.fg("accent", `"${args.name}"`)}`;
|
|
392
|
+
}
|
|
393
|
+
if (args.command) {
|
|
394
|
+
text += ` ${theme.fg("muted", args.command.slice(0, 40))}`;
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
case "output":
|
|
398
|
+
case "kill":
|
|
399
|
+
case "logs":
|
|
400
|
+
if (args.id) {
|
|
401
|
+
text += ` ${theme.fg("muted", args.id)}`;
|
|
402
|
+
}
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return new Text(text, 0, 0);
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
renderResult(
|
|
410
|
+
result: AgentToolResult<ProcessesDetails>,
|
|
411
|
+
_options: ToolRenderResultOptions,
|
|
412
|
+
theme: Theme,
|
|
413
|
+
): Text {
|
|
414
|
+
const { details } = result;
|
|
415
|
+
|
|
416
|
+
if (!details) {
|
|
417
|
+
const text = result.content[0];
|
|
418
|
+
return new Text(
|
|
419
|
+
text?.type === "text" && text.text ? text.text : "No result",
|
|
420
|
+
0,
|
|
421
|
+
0,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!details.success) {
|
|
426
|
+
return new Text(theme.fg("error", details.message), 0, 0);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// For start action
|
|
430
|
+
if (details.action === "start" && details.process) {
|
|
431
|
+
const p = details.process;
|
|
432
|
+
return new Text(
|
|
433
|
+
theme.fg("success", "\u2713 Started ") +
|
|
434
|
+
theme.fg("accent", `"${p.name}"`) +
|
|
435
|
+
` (${p.id}, PID: ${p.pid})`,
|
|
436
|
+
0,
|
|
437
|
+
0,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// For output action
|
|
442
|
+
if (details.action === "output" && details.output) {
|
|
443
|
+
const lines: string[] = [];
|
|
444
|
+
lines.push(theme.fg("muted", details.message));
|
|
445
|
+
|
|
446
|
+
if (details.output.stdout.length > 0) {
|
|
447
|
+
lines.push("");
|
|
448
|
+
lines.push(theme.fg("accent", "stdout:"));
|
|
449
|
+
const stdoutLines = details.output.stdout.slice(-20);
|
|
450
|
+
for (const line of stdoutLines) {
|
|
451
|
+
lines.push(line);
|
|
452
|
+
}
|
|
453
|
+
if (details.output.stdout.length > 20) {
|
|
454
|
+
lines.push(
|
|
455
|
+
theme.fg(
|
|
456
|
+
"muted",
|
|
457
|
+
`... (${details.output.stdout.length - 20} more lines)`,
|
|
458
|
+
),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (details.output.stderr.length > 0) {
|
|
464
|
+
lines.push("");
|
|
465
|
+
lines.push(theme.fg("warning", "stderr:"));
|
|
466
|
+
const stderrLines = details.output.stderr.slice(-10);
|
|
467
|
+
for (const line of stderrLines) {
|
|
468
|
+
lines.push(theme.fg("warning", line));
|
|
469
|
+
}
|
|
470
|
+
if (details.output.stderr.length > 10) {
|
|
471
|
+
lines.push(
|
|
472
|
+
theme.fg(
|
|
473
|
+
"muted",
|
|
474
|
+
`... (${details.output.stderr.length - 10} more lines)`,
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// For list action
|
|
484
|
+
if (
|
|
485
|
+
details.action === "list" &&
|
|
486
|
+
details.processes &&
|
|
487
|
+
details.processes.length > 0
|
|
488
|
+
) {
|
|
489
|
+
const lines: string[] = [];
|
|
490
|
+
lines.push(
|
|
491
|
+
theme.fg("success", `${details.processes.length} process(es):`),
|
|
492
|
+
);
|
|
493
|
+
for (const p of details.processes) {
|
|
494
|
+
const status =
|
|
495
|
+
p.status === "running"
|
|
496
|
+
? theme.fg("accent", "running")
|
|
497
|
+
: p.success
|
|
498
|
+
? theme.fg("success", "exit(0)")
|
|
499
|
+
: theme.fg("error", `exit(${p.exitCode})`);
|
|
500
|
+
lines.push(
|
|
501
|
+
` ${p.id} ${theme.fg("accent", `"${p.name}"`)}: ${truncateCmd(p.command)} [${status}] ${formatRuntime(p.startTime, p.endTime)}`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// For logs action
|
|
508
|
+
if (details.action === "logs" && details.logFiles) {
|
|
509
|
+
const lines: string[] = [];
|
|
510
|
+
lines.push(theme.fg("success", "Log files:"));
|
|
511
|
+
lines.push(
|
|
512
|
+
` stdout: ${theme.fg("accent", details.logFiles.stdoutFile)}`,
|
|
513
|
+
);
|
|
514
|
+
lines.push(
|
|
515
|
+
` stderr: ${theme.fg("accent", details.logFiles.stderrFile)}`,
|
|
516
|
+
);
|
|
517
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// For clear action
|
|
521
|
+
if (details.action === "clear") {
|
|
522
|
+
return new Text(
|
|
523
|
+
theme.fg("success", "\u2713 ") + theme.fg("muted", details.message),
|
|
524
|
+
0,
|
|
525
|
+
0,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return new Text(details.message, 0, 0);
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
}
|