letmecook 0.0.15 → 0.0.16
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/package.json +1 -1
- package/src/agents-md.ts +16 -27
- package/src/flows/add-repos.ts +132 -22
- package/src/flows/edit-session.ts +86 -15
- package/src/flows/new-session.ts +159 -34
- package/src/flows/resume-session.ts +53 -33
- package/src/git.ts +77 -39
- package/src/process-registry.ts +179 -0
- package/src/reference-repo.ts +288 -0
- package/src/tui-mode.ts +14 -1
- package/src/types.ts +2 -4
- package/src/ui/add-repos.ts +26 -70
- package/src/ui/common/command-runner.ts +270 -69
- package/src/ui/common/keyboard.ts +26 -0
- package/src/ui/common/repo-formatter.ts +4 -9
- package/src/ui/new-session.ts +2 -3
- package/src/ui/progress.ts +1 -1
- package/src/ui/session-settings.ts +2 -17
- package/src/utils/stream.ts +89 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { CliRenderer } from "@opentui/core";
|
|
2
2
|
import type { Session } from "../types";
|
|
3
3
|
import { updateSessionSettings } from "../sessions";
|
|
4
|
-
import {
|
|
4
|
+
import { refreshReferenceRepos } from "../git";
|
|
5
5
|
import { updateSkills } from "../skills";
|
|
6
6
|
import { handleSmartExit } from "../ui/exit";
|
|
7
7
|
import { showSessionSettings } from "../ui/session-settings";
|
|
8
8
|
import { showDeleteConfirm } from "../ui/confirm-delete";
|
|
9
|
+
import { showSessionStartWarning } from "../ui/background-warning";
|
|
9
10
|
import { showProgress, updateProgress, hideProgress } from "../ui/progress";
|
|
10
11
|
import { showReclonePrompt } from "../ui/reclone-prompt";
|
|
11
12
|
import { deleteSession } from "../sessions";
|
|
12
|
-
import { recloneRepo } from "../git";
|
|
13
13
|
import { writeAgentsMd } from "../agents-md";
|
|
14
14
|
import { createRenderer, destroyRenderer } from "../ui/renderer";
|
|
15
|
+
import { getProcessesForSession } from "../process-registry";
|
|
16
|
+
import { repairReferenceLink } from "../reference-repo";
|
|
15
17
|
|
|
16
18
|
export interface ResumeSessionParams {
|
|
17
19
|
session: Session;
|
|
@@ -30,13 +32,27 @@ export async function resumeSession(
|
|
|
30
32
|
let currentSession = session;
|
|
31
33
|
let shouldRefresh = initialRefresh;
|
|
32
34
|
|
|
35
|
+
// Check for background processes running for this session
|
|
36
|
+
if (mode === "tui") {
|
|
37
|
+
const runningProcesses = await getProcessesForSession(session.name);
|
|
38
|
+
if (runningProcesses.length > 0) {
|
|
39
|
+
renderer = await createRenderer();
|
|
40
|
+
const choice = await showSessionStartWarning(renderer, runningProcesses);
|
|
41
|
+
destroyRenderer();
|
|
42
|
+
if (choice === "cancel") {
|
|
43
|
+
return "home";
|
|
44
|
+
}
|
|
45
|
+
// "continue" proceeds with warning acknowledged
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
33
49
|
while (true) {
|
|
34
50
|
if (mode === "tui" && shouldRefresh) {
|
|
35
51
|
renderer = await createRenderer();
|
|
36
|
-
await
|
|
52
|
+
await refreshReferenceBeforeResume(renderer, currentSession);
|
|
37
53
|
destroyRenderer();
|
|
38
54
|
} else if (shouldRefresh) {
|
|
39
|
-
await
|
|
55
|
+
await refreshReferenceBeforeResumeSimple(currentSession);
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
await runOpencodeMode(mode, currentSession.path);
|
|
@@ -101,20 +117,23 @@ export async function resumeSession(
|
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
async function
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
async function refreshReferenceBeforeResume(
|
|
121
|
+
renderer: CliRenderer,
|
|
122
|
+
session: Session,
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const referenceRepos = session.repos.filter((repo) => repo.reference);
|
|
125
|
+
if (referenceRepos.length === 0) return;
|
|
107
126
|
|
|
108
|
-
const refreshProgressState = showProgress(renderer,
|
|
127
|
+
const refreshProgressState = showProgress(renderer, referenceRepos, {
|
|
109
128
|
title: "Refreshing repositories",
|
|
110
|
-
label: "Refreshing
|
|
129
|
+
label: "Refreshing reference repositories:",
|
|
111
130
|
initialPhase: "refreshing",
|
|
112
131
|
});
|
|
113
132
|
refreshProgressState.sessionName = session.name;
|
|
114
133
|
updateProgress(renderer, refreshProgressState);
|
|
115
134
|
|
|
116
|
-
const refreshResults = await
|
|
117
|
-
|
|
135
|
+
const refreshResults = await refreshReferenceRepos(
|
|
136
|
+
referenceRepos,
|
|
118
137
|
session.path,
|
|
119
138
|
(repoIndex, status, outputLines) => {
|
|
120
139
|
const repoState = refreshProgressState.repos[repoIndex];
|
|
@@ -134,59 +153,60 @@ async function refreshLatestBeforeResume(renderer: CliRenderer, session: Session
|
|
|
134
153
|
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
135
154
|
hideProgress(renderer);
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
// For reference repos that failed, offer to repair the link
|
|
157
|
+
const repairTargets = refreshResults.filter(
|
|
158
|
+
(result) => result.status === "error" && result.repo.reference,
|
|
139
159
|
);
|
|
140
160
|
|
|
141
|
-
for (const result of
|
|
161
|
+
for (const result of repairTargets) {
|
|
142
162
|
const choice = await showReclonePrompt(renderer, result.repo);
|
|
143
163
|
|
|
144
164
|
if (choice === "reclone") {
|
|
145
|
-
const
|
|
146
|
-
title: "
|
|
147
|
-
label: "
|
|
165
|
+
const repairProgressState = showProgress(renderer, [result.repo], {
|
|
166
|
+
title: "Repairing reference",
|
|
167
|
+
label: "Repairing:",
|
|
148
168
|
initialPhase: "cloning",
|
|
149
169
|
});
|
|
150
|
-
|
|
151
|
-
updateProgress(renderer,
|
|
170
|
+
repairProgressState.sessionName = session.name;
|
|
171
|
+
updateProgress(renderer, repairProgressState);
|
|
152
172
|
|
|
153
173
|
try {
|
|
154
|
-
await
|
|
155
|
-
const repoState =
|
|
174
|
+
await repairReferenceLink(result.repo, session.path, (status, outputLines) => {
|
|
175
|
+
const repoState = repairProgressState.repos[0];
|
|
156
176
|
if (repoState) {
|
|
157
177
|
repoState.status = status;
|
|
158
178
|
if (outputLines) {
|
|
159
|
-
|
|
179
|
+
repairProgressState.currentOutput = outputLines;
|
|
160
180
|
}
|
|
161
|
-
updateProgress(renderer,
|
|
181
|
+
updateProgress(renderer, repairProgressState);
|
|
162
182
|
}
|
|
163
183
|
});
|
|
164
184
|
} catch (error) {
|
|
165
|
-
const repoState =
|
|
185
|
+
const repoState = repairProgressState.repos[0];
|
|
166
186
|
if (repoState) {
|
|
167
187
|
repoState.status = "error";
|
|
168
188
|
}
|
|
169
|
-
|
|
189
|
+
repairProgressState.currentOutput = [
|
|
170
190
|
error instanceof Error ? error.message : String(error),
|
|
171
191
|
];
|
|
172
|
-
updateProgress(renderer,
|
|
192
|
+
updateProgress(renderer, repairProgressState);
|
|
173
193
|
}
|
|
174
194
|
|
|
175
|
-
|
|
176
|
-
updateProgress(renderer,
|
|
195
|
+
repairProgressState.phase = "done";
|
|
196
|
+
updateProgress(renderer, repairProgressState);
|
|
177
197
|
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
178
198
|
hideProgress(renderer);
|
|
179
199
|
}
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
|
|
183
|
-
async function
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
203
|
+
async function refreshReferenceBeforeResumeSimple(session: Session): Promise<void> {
|
|
204
|
+
const referenceRepos = session.repos.filter((repo) => repo.reference);
|
|
205
|
+
if (referenceRepos.length === 0) return;
|
|
186
206
|
|
|
187
|
-
console.log("\nRefreshing
|
|
207
|
+
console.log("\nRefreshing reference repositories...");
|
|
188
208
|
|
|
189
|
-
const results = await
|
|
209
|
+
const results = await refreshReferenceRepos(referenceRepos, session.path);
|
|
190
210
|
|
|
191
211
|
if (results.length === 0) return;
|
|
192
212
|
|
package/src/git.ts
CHANGED
|
@@ -193,63 +193,101 @@ export async function sessionHasUncommittedChanges(
|
|
|
193
193
|
return { hasChanges: reposWithChanges.length > 0, reposWithChanges };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
export async function
|
|
196
|
+
export async function refreshReferenceRepos(
|
|
197
197
|
repos: RepoSpec[],
|
|
198
198
|
sessionPath: string,
|
|
199
199
|
onProgress?: (repoIndex: number, status: RefreshProgressStatus, outputLines?: string[]) => void,
|
|
200
200
|
): Promise<RefreshResult[]> {
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
// Import reference-repo functions dynamically to avoid circular deps
|
|
202
|
+
const { refreshReferenceRepo, verifyReferenceLink, repairReferenceLink, isSymlink } =
|
|
203
|
+
await import("./reference-repo");
|
|
204
|
+
|
|
205
|
+
const referenceRepos = repos.filter((repo) => repo.reference);
|
|
206
|
+
if (referenceRepos.length === 0) return [];
|
|
203
207
|
|
|
204
208
|
const results: RefreshResult[] = [];
|
|
205
209
|
|
|
206
|
-
for (const [repoIndex, repo] of
|
|
210
|
+
for (const [repoIndex, repo] of referenceRepos.entries()) {
|
|
207
211
|
const repoPath = join(sessionPath, repo.dir);
|
|
208
|
-
const dirty = await hasUncommittedChanges(repoPath);
|
|
209
212
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
// Check if this is a symlink (reference repo)
|
|
214
|
+
const isRef = await isSymlink(repoPath);
|
|
215
|
+
|
|
216
|
+
if (isRef) {
|
|
217
|
+
// Verify symlink is valid, repair if needed
|
|
218
|
+
const isValid = await verifyReferenceLink(repo, sessionPath);
|
|
219
|
+
if (!isValid) {
|
|
220
|
+
onProgress?.(repoIndex, "refreshing", [`Repairing link to ${repo.owner}/${repo.name}...`]);
|
|
221
|
+
try {
|
|
222
|
+
await repairReferenceLink(repo, sessionPath, (status, lines) => {
|
|
223
|
+
if (status === "cloning") onProgress?.(repoIndex, "refreshing", lines);
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
results.push({
|
|
227
|
+
repo,
|
|
228
|
+
status: "error",
|
|
229
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
230
|
+
});
|
|
231
|
+
onProgress?.(repoIndex, "error", [
|
|
232
|
+
error instanceof Error ? error.message : String(error),
|
|
233
|
+
]);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Refresh the cached reference repo
|
|
239
|
+
const result = await refreshReferenceRepo(repo, (status, lines) => {
|
|
240
|
+
onProgress?.(repoIndex, status, lines);
|
|
215
241
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
242
|
+
results.push(result);
|
|
243
|
+
} else {
|
|
244
|
+
// Non-symlink reference repo (legacy or converted) - use standard git pull
|
|
245
|
+
const dirty = await hasUncommittedChanges(repoPath);
|
|
246
|
+
|
|
247
|
+
if (dirty) {
|
|
248
|
+
results.push({
|
|
249
|
+
repo,
|
|
250
|
+
status: "skipped",
|
|
251
|
+
reason: "uncommitted changes",
|
|
252
|
+
});
|
|
253
|
+
onProgress?.(repoIndex, "skipped", ["Skipped: uncommitted changes"]);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
219
256
|
|
|
220
|
-
|
|
257
|
+
onProgress?.(repoIndex, "refreshing", [`Pulling ${repo.owner}/${repo.name}...`]);
|
|
221
258
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
259
|
+
const proc = Bun.spawn(["git", "-C", repoPath, "pull", "--ff-only", "--depth", "1"], {
|
|
260
|
+
stdout: "pipe",
|
|
261
|
+
stderr: "pipe",
|
|
262
|
+
});
|
|
226
263
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
264
|
+
const { success, output, fullOutput } = await readProcessOutputWithBuffer(proc, {
|
|
265
|
+
maxBufferLines: 5,
|
|
266
|
+
onBufferUpdate: (buffer) => onProgress?.(repoIndex, "refreshing", buffer),
|
|
267
|
+
});
|
|
268
|
+
const exitCode = success ? 0 : 1;
|
|
269
|
+
|
|
270
|
+
if (exitCode !== 0) {
|
|
271
|
+
const reason = fullOutput.trim() || `git pull exited with code ${exitCode}`;
|
|
272
|
+
results.push({
|
|
273
|
+
repo,
|
|
274
|
+
status: "error",
|
|
275
|
+
reason,
|
|
276
|
+
});
|
|
277
|
+
onProgress?.(repoIndex, "error", output.length > 0 ? [...output] : [reason]);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const normalized = fullOutput.toLowerCase();
|
|
282
|
+
const upToDate =
|
|
283
|
+
normalized.includes("already up to date") || normalized.includes("already up-to-date");
|
|
232
284
|
|
|
233
|
-
if (exitCode !== 0) {
|
|
234
|
-
const reason = fullOutput.trim() || `git pull exited with code ${exitCode}`;
|
|
235
285
|
results.push({
|
|
236
286
|
repo,
|
|
237
|
-
status: "
|
|
238
|
-
reason,
|
|
287
|
+
status: upToDate ? "up-to-date" : "updated",
|
|
239
288
|
});
|
|
240
|
-
onProgress?.(repoIndex, "
|
|
241
|
-
continue;
|
|
289
|
+
onProgress?.(repoIndex, upToDate ? "up-to-date" : "updated", [...output]);
|
|
242
290
|
}
|
|
243
|
-
|
|
244
|
-
const normalized = fullOutput.toLowerCase();
|
|
245
|
-
const upToDate =
|
|
246
|
-
normalized.includes("already up to date") || normalized.includes("already up-to-date");
|
|
247
|
-
|
|
248
|
-
results.push({
|
|
249
|
-
repo,
|
|
250
|
-
status: upToDate ? "up-to-date" : "updated",
|
|
251
|
-
});
|
|
252
|
-
onProgress?.(repoIndex, upToDate ? "up-to-date" : "updated", [...output]);
|
|
253
291
|
}
|
|
254
292
|
|
|
255
293
|
return results;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdir } from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
const DATA_DIR = join(homedir(), ".letmecook");
|
|
7
|
+
const DB_PATH = join(DATA_DIR, "history.sqlite");
|
|
8
|
+
|
|
9
|
+
let db: Database | null = null;
|
|
10
|
+
|
|
11
|
+
export interface BackgroundProcess {
|
|
12
|
+
pid: number;
|
|
13
|
+
command: string;
|
|
14
|
+
description: string;
|
|
15
|
+
sessionName: string;
|
|
16
|
+
startTime: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DbRow {
|
|
20
|
+
pid: number;
|
|
21
|
+
command: string;
|
|
22
|
+
description: string;
|
|
23
|
+
session_name: string;
|
|
24
|
+
start_time: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function getDb(): Promise<Database> {
|
|
28
|
+
if (db) return db;
|
|
29
|
+
|
|
30
|
+
await mkdir(DATA_DIR, { recursive: true });
|
|
31
|
+
db = new Database(DB_PATH, { create: true });
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS background_processes (
|
|
34
|
+
pid INTEGER PRIMARY KEY,
|
|
35
|
+
command TEXT NOT NULL,
|
|
36
|
+
description TEXT NOT NULL,
|
|
37
|
+
session_name TEXT NOT NULL,
|
|
38
|
+
start_time TEXT NOT NULL
|
|
39
|
+
);
|
|
40
|
+
`);
|
|
41
|
+
|
|
42
|
+
return db;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function rowToProcess(row: DbRow): BackgroundProcess {
|
|
46
|
+
return {
|
|
47
|
+
pid: row.pid,
|
|
48
|
+
command: row.command,
|
|
49
|
+
description: row.description,
|
|
50
|
+
sessionName: row.session_name,
|
|
51
|
+
startTime: row.start_time,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isProcessAlive(pid: number): boolean {
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, 0);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function registerBackgroundProcess(
|
|
65
|
+
pid: number,
|
|
66
|
+
command: string,
|
|
67
|
+
description: string,
|
|
68
|
+
sessionName: string,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const database = await getDb();
|
|
71
|
+
const stmt = database.prepare(`
|
|
72
|
+
INSERT OR REPLACE INTO background_processes
|
|
73
|
+
(pid, command, description, session_name, start_time)
|
|
74
|
+
VALUES (?, ?, ?, ?, ?)
|
|
75
|
+
`);
|
|
76
|
+
stmt.run(pid, command, description, sessionName, new Date().toISOString());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function getRunningProcesses(): Promise<BackgroundProcess[]> {
|
|
80
|
+
const database = await getDb();
|
|
81
|
+
const stmt = database.prepare(`SELECT * FROM background_processes`);
|
|
82
|
+
const rows = stmt.all() as DbRow[];
|
|
83
|
+
|
|
84
|
+
const aliveProcesses: BackgroundProcess[] = [];
|
|
85
|
+
const deadPids: number[] = [];
|
|
86
|
+
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
if (isProcessAlive(row.pid)) {
|
|
89
|
+
aliveProcesses.push(rowToProcess(row));
|
|
90
|
+
} else {
|
|
91
|
+
deadPids.push(row.pid);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Clean up dead processes from the database
|
|
96
|
+
if (deadPids.length > 0) {
|
|
97
|
+
const deleteStmt = database.prepare(`DELETE FROM background_processes WHERE pid = ?`);
|
|
98
|
+
const deleteMany = database.transaction((pids: number[]) => {
|
|
99
|
+
for (const pid of pids) {
|
|
100
|
+
deleteStmt.run(pid);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
deleteMany(deadPids);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return aliveProcesses;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function getProcessesForSession(sessionName: string): Promise<BackgroundProcess[]> {
|
|
110
|
+
const database = await getDb();
|
|
111
|
+
const stmt = database.prepare(`SELECT * FROM background_processes WHERE session_name = ?`);
|
|
112
|
+
const rows = stmt.all(sessionName) as DbRow[];
|
|
113
|
+
|
|
114
|
+
const aliveProcesses: BackgroundProcess[] = [];
|
|
115
|
+
const deadPids: number[] = [];
|
|
116
|
+
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
if (isProcessAlive(row.pid)) {
|
|
119
|
+
aliveProcesses.push(rowToProcess(row));
|
|
120
|
+
} else {
|
|
121
|
+
deadPids.push(row.pid);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Clean up dead processes from the database
|
|
126
|
+
if (deadPids.length > 0) {
|
|
127
|
+
const deleteStmt = database.prepare(`DELETE FROM background_processes WHERE pid = ?`);
|
|
128
|
+
const deleteMany = database.transaction((pids: number[]) => {
|
|
129
|
+
for (const pid of pids) {
|
|
130
|
+
deleteStmt.run(pid);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
deleteMany(deadPids);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return aliveProcesses;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function killProcess(pid: number): Promise<boolean> {
|
|
140
|
+
try {
|
|
141
|
+
// First try SIGTERM for graceful shutdown
|
|
142
|
+
process.kill(pid, "SIGTERM");
|
|
143
|
+
|
|
144
|
+
// Wait up to 3 seconds for process to exit
|
|
145
|
+
const startTime = Date.now();
|
|
146
|
+
while (Date.now() - startTime < 3000) {
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
148
|
+
if (!isProcessAlive(pid)) {
|
|
149
|
+
await removeProcessFromRegistry(pid);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Process didn't exit, send SIGKILL
|
|
155
|
+
process.kill(pid, "SIGKILL");
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
157
|
+
|
|
158
|
+
await removeProcessFromRegistry(pid);
|
|
159
|
+
return true;
|
|
160
|
+
} catch {
|
|
161
|
+
// Process may have already exited
|
|
162
|
+
await removeProcessFromRegistry(pid);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function killAllProcesses(): Promise<void> {
|
|
168
|
+
const runningProcesses = await getRunningProcesses();
|
|
169
|
+
|
|
170
|
+
for (const proc of runningProcesses) {
|
|
171
|
+
await killProcess(proc.pid);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function removeProcessFromRegistry(pid: number): Promise<void> {
|
|
176
|
+
const database = await getDb();
|
|
177
|
+
const stmt = database.prepare(`DELETE FROM background_processes WHERE pid = ?`);
|
|
178
|
+
stmt.run(pid);
|
|
179
|
+
}
|