@jmfederico/pi-web 1.202606.0 → 1.202606.2
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 +23 -7
- package/dist/client/assets/{CodeViewer-N88Bx8F6.js → CodeViewer-iYSv2-dg.js} +1 -1
- package/dist/client/assets/{TerminalPanel-DKiL51Xi.js → TerminalPanel-BpFW6j27.js} +4 -4
- package/dist/client/assets/index-D4dbiKXF.js +2210 -0
- package/dist/client/index.html +1 -1
- package/dist/config.js +63 -1
- package/dist/config.js.map +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +8 -1
- package/dist/pi-web-plugins/updates/package.json +9 -0
- package/dist/pi-web-plugins/{pi-web → updates}/pi-web-plugin.js +38 -30
- package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +292 -0
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
- package/dist/plugin-api/unstable.d.ts +22 -0
- package/dist/plugin-api.d.ts +169 -0
- package/dist/server/app.js +63 -26
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +123 -0
- package/dist/server/configRoutes.js.map +1 -0
- package/dist/server/git/gitEnv.js +15 -0
- package/dist/server/git/gitEnv.js.map +1 -0
- package/dist/server/git/gitService.js +2 -1
- package/dist/server/git/gitService.js.map +1 -1
- package/dist/server/gitRoutes.js +3 -3
- package/dist/server/gitRoutes.js.map +1 -1
- package/dist/server/machines/machineClient.js +134 -0
- package/dist/server/machines/machineClient.js.map +1 -0
- package/dist/server/machines/machinePluginProxyRoutes.js +187 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineProxyRoutes.js +92 -0
- package/dist/server/machines/machineProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineRoutes.js +56 -0
- package/dist/server/machines/machineRoutes.js.map +1 -0
- package/dist/server/machines/machineService.js +260 -0
- package/dist/server/machines/machineService.js.map +1 -0
- package/dist/server/machines/machineStore.js +128 -0
- package/dist/server/machines/machineStore.js.map +1 -0
- package/dist/server/piWebPluginService.js +45 -15
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +127 -36
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/piWebStatusCache.js +32 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +17 -14
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +18 -7
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +87 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +147 -66
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionArchiveStore.js +12 -0
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +2 -0
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +126 -43
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/terminalProxyRoutes.js +20 -10
- package/dist/server/terminalProxyRoutes.js.map +1 -1
- package/dist/server/terminals/terminalRoutes.js +11 -0
- package/dist/server/terminals/terminalRoutes.js.map +1 -1
- package/dist/server/terminals/terminalService.js +6 -0
- package/dist/server/terminals/terminalService.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +4 -4
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/fileSuggestions.js +98 -17
- package/dist/server/workspaces/fileSuggestions.js.map +1 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +3 -2
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +1 -1
- package/dist/server/workspaces/workspaceDeletionRoutes.js +113 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +498 -0
- package/dist/shared/apiTypes.js +3 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +25 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +61 -0
- package/dist/shared/federatedRoutes.js.map +1 -0
- package/dist/shared/machinePluginIds.js +41 -0
- package/dist/shared/machinePluginIds.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +43 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginIds.js +5 -0
- package/dist/shared/pluginIds.js.map +1 -0
- package/dist/shared/workspaceDeletion.js +12 -0
- package/dist/shared/workspaceDeletion.js.map +1 -0
- package/docs/plugins.md +278 -71
- package/package.json +8 -12
- package/plugin-api/unstable.d.ts +1 -0
- package/plugin-api.d.ts +1 -148
- package/dist/client/assets/index-DeSR1RUE.js +0 -1446
- package/dist/pi-web-plugins/pi-web/package.json +0 -9
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/workspace-tasks/tasksPanelElement.ts. Do not edit directly.
|
|
2
|
+
import { TASKS_CONFIG_PATH } from "./config.js";
|
|
3
|
+
import { runWorkspaceTaskInTerminal } from "./taskRunner.js";
|
|
4
|
+
import { loadWorkspaceTasksConfig, tasksConfigRefreshHint, tasksConfigUnavailableMessage } from "./workspaceTasksClient.js";
|
|
5
|
+
export const tasksPanelTagName = "pi-web-workspace-tasks-panel";
|
|
6
|
+
const configChangedEvent = "pi-web-workspace-tasks-config-changed";
|
|
7
|
+
const configCache = new Map();
|
|
8
|
+
export function defineTasksPanelElement() {
|
|
9
|
+
if (!customElements.get(tasksPanelTagName))
|
|
10
|
+
customElements.define(tasksPanelTagName, PiWebTasksPanel);
|
|
11
|
+
}
|
|
12
|
+
export function tasksPanelBadge(context) {
|
|
13
|
+
const state = getCachedWorkspaceConfig(context);
|
|
14
|
+
if (state?.kind === "unavailable")
|
|
15
|
+
return "!";
|
|
16
|
+
if (state?.kind === "loaded" && state.config.tasks.length > 0)
|
|
17
|
+
return state.config.tasks.length;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
class PiWebTasksPanel extends HTMLElement {
|
|
21
|
+
contextValue;
|
|
22
|
+
runningTaskId;
|
|
23
|
+
status;
|
|
24
|
+
root;
|
|
25
|
+
onConfigChanged = () => {
|
|
26
|
+
this.render();
|
|
27
|
+
};
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
31
|
+
}
|
|
32
|
+
set context(value) {
|
|
33
|
+
const previousKey = this.contextValue === undefined ? undefined : cacheKeyForContext(this.contextValue);
|
|
34
|
+
const nextKey = value === undefined ? undefined : cacheKeyForContext(value);
|
|
35
|
+
this.contextValue = value;
|
|
36
|
+
// Parent app updates should not rebuild this shadow DOM for the same workspace:
|
|
37
|
+
// doing so resets the mobile scroll position and can replace buttons mid-click.
|
|
38
|
+
if (previousKey === nextKey)
|
|
39
|
+
return;
|
|
40
|
+
this.runningTaskId = undefined;
|
|
41
|
+
this.status = undefined;
|
|
42
|
+
this.render();
|
|
43
|
+
}
|
|
44
|
+
connectedCallback() {
|
|
45
|
+
window.addEventListener(configChangedEvent, this.onConfigChanged);
|
|
46
|
+
this.render();
|
|
47
|
+
}
|
|
48
|
+
disconnectedCallback() {
|
|
49
|
+
window.removeEventListener(configChangedEvent, this.onConfigChanged);
|
|
50
|
+
}
|
|
51
|
+
render() {
|
|
52
|
+
const context = this.contextValue;
|
|
53
|
+
if (context === undefined) {
|
|
54
|
+
this.root.innerHTML = `${taskStyles()}<section class="empty">Select a workspace.</section>`;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const state = getOrLoadWorkspaceConfig(context);
|
|
58
|
+
this.root.innerHTML = `
|
|
59
|
+
${taskStyles()}
|
|
60
|
+
<section class="toolbar">
|
|
61
|
+
<strong>Workspace Tasks</strong>
|
|
62
|
+
<span class="toolbar-tasks">
|
|
63
|
+
<button class="secondary" data-refresh-config ${state.kind === "loading" ? "disabled" : ""}>Refresh</button>
|
|
64
|
+
<button class="secondary" data-open-terminal>Open Terminal</button>
|
|
65
|
+
</span>
|
|
66
|
+
</section>
|
|
67
|
+
${this.renderStatus()}
|
|
68
|
+
<section class="viewer tasks-viewer">
|
|
69
|
+
${this.renderConfigState(state)}
|
|
70
|
+
</section>
|
|
71
|
+
`;
|
|
72
|
+
this.root.querySelector("button[data-refresh-config]")?.addEventListener("click", () => {
|
|
73
|
+
void this.refreshConfig(context);
|
|
74
|
+
});
|
|
75
|
+
for (const button of this.root.querySelectorAll("button[data-task-id]")) {
|
|
76
|
+
button.addEventListener("click", () => {
|
|
77
|
+
void this.dispatchTaskById(context, button.getAttribute("data-task-id"));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
this.root.querySelector("button[data-open-terminal]")?.addEventListener("click", () => {
|
|
81
|
+
this.openWorkspaceTerminal();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
dispatchTaskById(context, taskId) {
|
|
85
|
+
if (!this.isCurrentContext(context))
|
|
86
|
+
return Promise.resolve();
|
|
87
|
+
const task = taskFromConfigState(getCachedWorkspaceConfig(context), taskId);
|
|
88
|
+
if (task === undefined) {
|
|
89
|
+
this.status = { kind: "error", message: "That task is no longer available. Click Refresh, then try again." };
|
|
90
|
+
this.render();
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
}
|
|
93
|
+
return this.dispatchTask(context, task);
|
|
94
|
+
}
|
|
95
|
+
isCurrentContext(context) {
|
|
96
|
+
return this.contextValue !== undefined && cacheKeyForContext(this.contextValue) === cacheKeyForContext(context);
|
|
97
|
+
}
|
|
98
|
+
renderConfigState(state) {
|
|
99
|
+
if (state.kind === "loading")
|
|
100
|
+
return `<p class="muted">Loading ${escapeHtml(TASKS_CONFIG_PATH)}…</p>`;
|
|
101
|
+
if (state.kind === "missing")
|
|
102
|
+
return renderMissingState(state);
|
|
103
|
+
if (state.kind === "unavailable")
|
|
104
|
+
return renderUnavailableState(state);
|
|
105
|
+
if (state.config.tasks.length === 0)
|
|
106
|
+
return `<p class="muted">No tasks are defined in ${escapeHtml(state.path)}. Add tasks to the file, then click Refresh.</p>`;
|
|
107
|
+
return `
|
|
108
|
+
<p class="muted">Tasks run in a dedicated workspace terminal, then switch to that terminal. Edit ${escapeHtml(state.path)} and click Refresh to reload.</p>
|
|
109
|
+
${renderTaskGroups(state.config.tasks, this.runningTaskId)}
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
renderStatus() {
|
|
113
|
+
if (this.status === undefined)
|
|
114
|
+
return "";
|
|
115
|
+
const detail = this.status.detail === undefined ? "" : `<pre>${escapeHtml(this.status.detail)}</pre>`;
|
|
116
|
+
return `<div class="status panel-status ${escapeAttr(this.status.kind)}">${escapeHtml(this.status.message)}${detail}</div>`;
|
|
117
|
+
}
|
|
118
|
+
async refreshConfig(context) {
|
|
119
|
+
this.status = { kind: "info", message: `Refreshing ${TASKS_CONFIG_PATH}…` };
|
|
120
|
+
configCache.set(cacheKeyForContext(context), { kind: "loading" });
|
|
121
|
+
this.render();
|
|
122
|
+
const state = await refreshWorkspaceConfig(context);
|
|
123
|
+
if (!this.isCurrentContext(context))
|
|
124
|
+
return;
|
|
125
|
+
this.status = state.kind === "loaded"
|
|
126
|
+
? { kind: "success", message: `Loaded ${String(state.config.tasks.length)} task${state.config.tasks.length === 1 ? "" : "s"}.` }
|
|
127
|
+
: undefined;
|
|
128
|
+
this.render();
|
|
129
|
+
}
|
|
130
|
+
async dispatchTask(context, task) {
|
|
131
|
+
if (this.runningTaskId !== undefined) {
|
|
132
|
+
this.status = { kind: "info", message: "Another task is already starting. Wait for it to finish dispatching, then try again." };
|
|
133
|
+
this.render();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (task.confirm && !window.confirm(`Run ${task.title}?\n\n${task.command}`)) {
|
|
137
|
+
this.status = { kind: "info", message: `Cancelled ${task.title}.` };
|
|
138
|
+
this.render();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.runningTaskId = task.id;
|
|
142
|
+
this.status = { kind: "info", message: `Starting ${task.title}…` };
|
|
143
|
+
this.render();
|
|
144
|
+
try {
|
|
145
|
+
const handle = await runWorkspaceTaskInTerminal(context.terminal, task);
|
|
146
|
+
if (!this.isCurrentContext(context))
|
|
147
|
+
return;
|
|
148
|
+
this.status = {
|
|
149
|
+
kind: "success",
|
|
150
|
+
message: `Started terminal command “${handle.run.title}”.`,
|
|
151
|
+
detail: task.command,
|
|
152
|
+
};
|
|
153
|
+
this.runningTaskId = undefined;
|
|
154
|
+
this.render();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (!this.isCurrentContext(context))
|
|
158
|
+
return;
|
|
159
|
+
this.runningTaskId = undefined;
|
|
160
|
+
this.status = { kind: "error", message: error instanceof Error ? error.message : String(error) };
|
|
161
|
+
this.render();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
openWorkspaceTerminal(terminalId) {
|
|
165
|
+
const context = this.contextValue;
|
|
166
|
+
if (context === undefined) {
|
|
167
|
+
this.status = { kind: "error", message: "Select a workspace before opening a terminal." };
|
|
168
|
+
this.render();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (terminalId === undefined)
|
|
172
|
+
context.terminal.open();
|
|
173
|
+
else
|
|
174
|
+
context.terminal.open({ terminalId });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function getCachedWorkspaceConfig(context) {
|
|
178
|
+
return configCache.get(cacheKeyForContext(context));
|
|
179
|
+
}
|
|
180
|
+
function getOrLoadWorkspaceConfig(context) {
|
|
181
|
+
const cached = getCachedWorkspaceConfig(context);
|
|
182
|
+
if (cached !== undefined)
|
|
183
|
+
return cached;
|
|
184
|
+
const loading = { kind: "loading" };
|
|
185
|
+
configCache.set(cacheKeyForContext(context), loading);
|
|
186
|
+
void refreshWorkspaceConfig(context);
|
|
187
|
+
return loading;
|
|
188
|
+
}
|
|
189
|
+
async function refreshWorkspaceConfig(context) {
|
|
190
|
+
const key = cacheKeyForContext(context);
|
|
191
|
+
const state = await loadWorkspaceTasksConfig(context.files).catch((error) => ({
|
|
192
|
+
kind: "unavailable",
|
|
193
|
+
message: tasksConfigUnavailableMessage,
|
|
194
|
+
hint: tasksConfigRefreshHint,
|
|
195
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
196
|
+
}));
|
|
197
|
+
configCache.set(key, state);
|
|
198
|
+
context.host.requestRender();
|
|
199
|
+
window.dispatchEvent(new Event(configChangedEvent));
|
|
200
|
+
return state;
|
|
201
|
+
}
|
|
202
|
+
function cacheKeyForContext(context) {
|
|
203
|
+
return `${context.machine.id}:${context.workspace.projectId}:${context.workspace.id}`;
|
|
204
|
+
}
|
|
205
|
+
function renderMissingState(state) {
|
|
206
|
+
return `<div class="empty-state"><strong>${escapeHtml(state.message)}</strong><p>${escapeHtml(state.hint)}</p></div>`;
|
|
207
|
+
}
|
|
208
|
+
function renderUnavailableState(state) {
|
|
209
|
+
const detail = state.detail === undefined ? "" : `<pre>${escapeHtml(state.detail)}</pre>`;
|
|
210
|
+
return `<div class="status error"><strong>${escapeHtml(state.message)}</strong><p>${escapeHtml(state.hint)}</p>${detail}</div>`;
|
|
211
|
+
}
|
|
212
|
+
function renderTaskGroups(tasks, runningTaskId) {
|
|
213
|
+
return `<div class="tasks">${groupTasks(tasks).map((group) => renderTaskGroup(group, runningTaskId)).join("")}</div>`;
|
|
214
|
+
}
|
|
215
|
+
function groupTasks(tasks) {
|
|
216
|
+
const groups = [];
|
|
217
|
+
for (const task of tasks) {
|
|
218
|
+
const title = task.group;
|
|
219
|
+
let group = groups.find((candidate) => candidate.title === title);
|
|
220
|
+
if (group === undefined) {
|
|
221
|
+
group = { title, tasks: [] };
|
|
222
|
+
groups.push(group);
|
|
223
|
+
}
|
|
224
|
+
group.tasks.push(task);
|
|
225
|
+
}
|
|
226
|
+
return groups;
|
|
227
|
+
}
|
|
228
|
+
function renderTaskGroup(group, runningTaskId) {
|
|
229
|
+
const title = group.title === undefined ? "" : `<h3>${escapeHtml(group.title)}</h3>`;
|
|
230
|
+
return `<section class="task-group">${title}${group.tasks.map((task) => renderTask(task, runningTaskId)).join("")}</section>`;
|
|
231
|
+
}
|
|
232
|
+
function renderTask(task, runningTaskId) {
|
|
233
|
+
const running = runningTaskId === task.id;
|
|
234
|
+
const disabled = runningTaskId !== undefined;
|
|
235
|
+
const description = task.description === undefined ? "" : `<span>${escapeHtml(task.description)}</span>`;
|
|
236
|
+
return `
|
|
237
|
+
<article class="task-card">
|
|
238
|
+
<div class="task-copy">
|
|
239
|
+
<strong>${escapeHtml(task.title)}</strong>
|
|
240
|
+
${description}
|
|
241
|
+
<code>${escapeHtml(task.command)}</code>
|
|
242
|
+
</div>
|
|
243
|
+
<button data-task-id="${escapeAttr(task.id)}" ${disabled ? "disabled" : ""}>${running ? "Dispatching…" : "Run"}</button>
|
|
244
|
+
</article>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
function taskFromConfigState(state, taskId) {
|
|
248
|
+
if (state?.kind !== "loaded" || taskId === null)
|
|
249
|
+
return undefined;
|
|
250
|
+
return state.config.tasks.find((task) => task.id === taskId);
|
|
251
|
+
}
|
|
252
|
+
function taskStyles() {
|
|
253
|
+
return `
|
|
254
|
+
<style>
|
|
255
|
+
:host { display: contents; }
|
|
256
|
+
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--pi-border-muted); }
|
|
257
|
+
.toolbar-tasks { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
|
258
|
+
.viewer { box-sizing: border-box; min-height: 0; overflow: auto; padding: 12px; }
|
|
259
|
+
.tasks-viewer { display: grid; align-content: start; gap: 12px; }
|
|
260
|
+
.tasks { display: grid; gap: 14px; }
|
|
261
|
+
.task-group { display: grid; gap: 10px; }
|
|
262
|
+
.task-group h3 { margin: 4px 0 0; color: var(--pi-text-secondary); font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
263
|
+
.task-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center; border: 1px solid var(--pi-border); border-radius: 10px; background: var(--pi-surface); padding: 12px; }
|
|
264
|
+
.task-copy { display: grid; min-width: 0; gap: 5px; }
|
|
265
|
+
.task-copy span, .muted { color: var(--pi-muted); }
|
|
266
|
+
code, pre { border: 1px solid var(--pi-border-muted); border-radius: 6px; background: var(--pi-bg); color: var(--pi-text-secondary); font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
267
|
+
code { overflow: auto; padding: 5px 7px; white-space: nowrap; }
|
|
268
|
+
pre { margin: 8px 0 0; overflow: auto; padding: 8px; white-space: pre-wrap; }
|
|
269
|
+
button { border: 1px solid var(--pi-accent-border); border-radius: 7px; background: var(--pi-accent); color: var(--pi-bg); cursor: pointer; padding: 6px 10px; font: inherit; }
|
|
270
|
+
button.secondary { border-color: var(--pi-border); background: var(--pi-surface); color: var(--pi-text); }
|
|
271
|
+
button:disabled { cursor: wait; opacity: 0.65; }
|
|
272
|
+
.empty-state { border: 1px dashed var(--pi-border-muted); border-radius: 8px; color: var(--pi-muted); padding: 12px; }
|
|
273
|
+
.empty-state p { margin: 6px 0 0; }
|
|
274
|
+
.panel-status { margin: 12px 12px 0; }
|
|
275
|
+
.status { border: 1px solid var(--pi-border); border-radius: 8px; padding: 10px; }
|
|
276
|
+
.status.info { border-color: var(--pi-accent-border); background: var(--pi-bg-overlay-soft); }
|
|
277
|
+
.status.success { border-color: var(--pi-success-border); background: var(--pi-success-surface); color: var(--pi-success); }
|
|
278
|
+
.status.error { border-color: var(--pi-danger); color: var(--pi-danger); }
|
|
279
|
+
.empty { padding: 16px; color: var(--pi-muted); }
|
|
280
|
+
@media (max-width: 760px) {
|
|
281
|
+
.task-card { grid-template-columns: 1fr; }
|
|
282
|
+
.task-card button { justify-self: start; }
|
|
283
|
+
}
|
|
284
|
+
</style>
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
function escapeHtml(value) {
|
|
288
|
+
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
289
|
+
}
|
|
290
|
+
function escapeAttr(value) {
|
|
291
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
292
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/workspace-tasks/workspaceTasksClient.ts. Do not edit directly.
|
|
2
|
+
import { TASKS_CONFIG_PATH, parseTasksConfigText } from "./config.js";
|
|
3
|
+
export const tasksConfigMissingMessage = "No workspace tasks configured here.";
|
|
4
|
+
export const tasksConfigMissingHint = `${TASKS_CONFIG_PATH} is optional. Create it in this workspace if you want custom tasks.`;
|
|
5
|
+
export const tasksConfigUnavailableMessage = "Could not load workspace tasks.";
|
|
6
|
+
export const tasksConfigRefreshHint = `Fix ${TASKS_CONFIG_PATH}, then click Refresh.`;
|
|
7
|
+
const missingWorkspaceFileError = "Path does not exist";
|
|
8
|
+
export async function loadWorkspaceTasksConfig(files) {
|
|
9
|
+
let file;
|
|
10
|
+
try {
|
|
11
|
+
file = await files.readFile(TASKS_CONFIG_PATH);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (errorMessage(error) === missingWorkspaceFileError)
|
|
15
|
+
return missing();
|
|
16
|
+
return unavailable(`Unable to read ${TASKS_CONFIG_PATH}: ${formatUnknownError(error)}`);
|
|
17
|
+
}
|
|
18
|
+
if (file.binary)
|
|
19
|
+
return unavailable(`${TASKS_CONFIG_PATH} must be a text file`);
|
|
20
|
+
if (file.truncated)
|
|
21
|
+
return unavailable(`${TASKS_CONFIG_PATH} is too large and was truncated`);
|
|
22
|
+
const result = parseTasksConfigText(file.content);
|
|
23
|
+
if (!result.ok)
|
|
24
|
+
return unavailable(result.error);
|
|
25
|
+
return { kind: "loaded", config: result.config, path: TASKS_CONFIG_PATH };
|
|
26
|
+
}
|
|
27
|
+
function missing() {
|
|
28
|
+
return {
|
|
29
|
+
kind: "missing",
|
|
30
|
+
message: tasksConfigMissingMessage,
|
|
31
|
+
hint: tasksConfigMissingHint,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function unavailable(detail) {
|
|
35
|
+
return {
|
|
36
|
+
kind: "unavailable",
|
|
37
|
+
message: tasksConfigUnavailableMessage,
|
|
38
|
+
hint: tasksConfigRefreshHint,
|
|
39
|
+
detail,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function errorMessage(error) {
|
|
43
|
+
return error instanceof Error ? error.message : undefined;
|
|
44
|
+
}
|
|
45
|
+
function formatUnknownError(error) {
|
|
46
|
+
return error instanceof Error ? error.message : String(error);
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TerminalCommandRun, TerminalCommandRunFilter, TerminalCommandRunHandle, Workspace, WorkspaceTerminalCommandInput } from "../plugin-api.js";
|
|
2
|
+
export interface UnstableRunTerminalCommandInput extends WorkspaceTerminalCommandInput {
|
|
3
|
+
workspace: Workspace;
|
|
4
|
+
}
|
|
5
|
+
export interface UnstableTerminalCommandRunsRuntime {
|
|
6
|
+
runCommand(input: UnstableRunTerminalCommandInput): Promise<TerminalCommandRunHandle>;
|
|
7
|
+
listCommandRuns(filter?: TerminalCommandRunFilter): Promise<TerminalCommandRun[]>;
|
|
8
|
+
getCommandRun(runId: string): Promise<TerminalCommandRun | undefined>;
|
|
9
|
+
open(options?: {
|
|
10
|
+
terminalId?: string | undefined;
|
|
11
|
+
}): void;
|
|
12
|
+
}
|
|
13
|
+
export interface UnstableRuntimeCapabilities {
|
|
14
|
+
terminalCommandRuns: UnstableTerminalCommandRunsRuntime;
|
|
15
|
+
openSettings?: (section?: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface UnstablePluginRuntimeContext {
|
|
18
|
+
piWebUnstable?: UnstableRuntimeCapabilities;
|
|
19
|
+
}
|
|
20
|
+
export interface UnstableWorkspacePanelContext {
|
|
21
|
+
piWebUnstable?: Pick<UnstableRuntimeCapabilities, "terminalCommandRuns">;
|
|
22
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { TemplateResult } from "lit";
|
|
2
|
+
import type { FileContentResponse, MachineKind, PiWebStatusResponse, TerminalCommandRunHandle } from "./shared/apiTypes.js";
|
|
3
|
+
export type { FileContentMediaType, FileContentResponse, FileTreeEntry, FileTreeResponse, MachineKind, PiWebComponentStatus, PiWebInstallationInfo, PiWebInstallationKind, PiWebReleaseStatus, PiWebServiceComponent, PiWebStatusMessage, PiWebStatusResponse, PiWebStatusSeverity, PiWebVersionResponse, TerminalCommandRun, TerminalCommandRunFilter, TerminalCommandRunHandle, TerminalCommandRunStatus, } from "./shared/apiTypes.js";
|
|
4
|
+
export type PluginId = string;
|
|
5
|
+
export type LocalContributionId = string;
|
|
6
|
+
export type QualifiedContributionId = string;
|
|
7
|
+
export type HtmlTemplateTag = (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
|
|
8
|
+
export type SvgTemplateTag = (strings: TemplateStringsArray, ...values: unknown[]) => TemplateResult;
|
|
9
|
+
export interface PiWebPlugin {
|
|
10
|
+
apiVersion: 1;
|
|
11
|
+
name: string;
|
|
12
|
+
activate: (context: PluginActivationContext) => PluginActivationResult;
|
|
13
|
+
}
|
|
14
|
+
export interface PluginActivationContext {
|
|
15
|
+
apiVersion: 1;
|
|
16
|
+
pluginId: PluginId;
|
|
17
|
+
html: HtmlTemplateTag;
|
|
18
|
+
svg: SvgTemplateTag;
|
|
19
|
+
}
|
|
20
|
+
export interface PluginActivationResult {
|
|
21
|
+
contributions: PluginContributions;
|
|
22
|
+
}
|
|
23
|
+
export interface PluginContributions {
|
|
24
|
+
actions?: PluginAction[];
|
|
25
|
+
workspacePanels?: WorkspacePanelContribution[];
|
|
26
|
+
workspaceLabels?: WorkspaceLabelContribution[];
|
|
27
|
+
themes?: ThemeContribution[];
|
|
28
|
+
themePairs?: ThemePairContribution[];
|
|
29
|
+
}
|
|
30
|
+
export interface PluginMachine {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
kind: MachineKind;
|
|
34
|
+
}
|
|
35
|
+
export interface PluginRuntimeState {
|
|
36
|
+
selectedWorkspace?: Workspace;
|
|
37
|
+
selectedSession?: unknown;
|
|
38
|
+
workspaceTool?: string;
|
|
39
|
+
mainView?: string;
|
|
40
|
+
piWebStatus?: PiWebStatusResponse;
|
|
41
|
+
}
|
|
42
|
+
export interface PluginRuntimeContext {
|
|
43
|
+
state: PluginRuntimeState;
|
|
44
|
+
openActionPalette: () => void;
|
|
45
|
+
focusPrompt: () => void;
|
|
46
|
+
addProject: () => void | Promise<void>;
|
|
47
|
+
configureAuth: () => void | Promise<void>;
|
|
48
|
+
logoutAuth: () => void | Promise<void>;
|
|
49
|
+
openThemePicker: () => void;
|
|
50
|
+
selectMainView: (view: string) => void;
|
|
51
|
+
selectWorkspaceTool: (tool: QualifiedContributionId) => void;
|
|
52
|
+
openTerminal: (options?: {
|
|
53
|
+
terminalId?: string | undefined;
|
|
54
|
+
}) => void;
|
|
55
|
+
refreshFiles: () => void | Promise<void>;
|
|
56
|
+
refreshGit: () => void | Promise<void>;
|
|
57
|
+
refreshAppData: () => void | Promise<void>;
|
|
58
|
+
reloadPage: () => void;
|
|
59
|
+
startSession: () => void | Promise<void>;
|
|
60
|
+
archiveSession: () => void | Promise<void>;
|
|
61
|
+
stopActiveWork: () => void | Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export interface PluginAction {
|
|
64
|
+
id: LocalContributionId;
|
|
65
|
+
title: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
shortcut?: string;
|
|
68
|
+
group?: string;
|
|
69
|
+
enabled?: (context: PluginRuntimeContext) => boolean;
|
|
70
|
+
run: (context: PluginRuntimeContext) => void | Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export interface Workspace {
|
|
73
|
+
id: string;
|
|
74
|
+
projectId: string;
|
|
75
|
+
path: string;
|
|
76
|
+
label: string;
|
|
77
|
+
branch?: string;
|
|
78
|
+
isMain: boolean;
|
|
79
|
+
isGitRepo: boolean;
|
|
80
|
+
isGitWorktree: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface WorkspaceFiles {
|
|
83
|
+
readFile(path: string): Promise<FileContentResponse>;
|
|
84
|
+
}
|
|
85
|
+
export type WorkspacePanelFiles = WorkspaceFiles;
|
|
86
|
+
export interface WorkspaceHost {
|
|
87
|
+
requestRender(): void;
|
|
88
|
+
}
|
|
89
|
+
export type WorkspacePanelHost = WorkspaceHost;
|
|
90
|
+
export interface WorkspaceContext {
|
|
91
|
+
machine: PluginMachine;
|
|
92
|
+
workspace: Workspace;
|
|
93
|
+
state?: PluginRuntimeState;
|
|
94
|
+
files: WorkspaceFiles;
|
|
95
|
+
host: WorkspaceHost;
|
|
96
|
+
}
|
|
97
|
+
export interface WorkspaceTerminalCommandInput {
|
|
98
|
+
title: string;
|
|
99
|
+
command: string;
|
|
100
|
+
metadata?: Record<string, string>;
|
|
101
|
+
open?: boolean;
|
|
102
|
+
}
|
|
103
|
+
export interface WorkspacePanelTerminal {
|
|
104
|
+
open(options?: {
|
|
105
|
+
terminalId?: string | undefined;
|
|
106
|
+
}): void;
|
|
107
|
+
runCommand(input: WorkspaceTerminalCommandInput): Promise<TerminalCommandRunHandle>;
|
|
108
|
+
}
|
|
109
|
+
export interface WorkspacePanelContext extends WorkspaceContext {
|
|
110
|
+
terminal: WorkspacePanelTerminal;
|
|
111
|
+
}
|
|
112
|
+
export type WorkspacePanelIcon = TemplateResult;
|
|
113
|
+
export interface WorkspacePanelContribution {
|
|
114
|
+
id: LocalContributionId;
|
|
115
|
+
title: string;
|
|
116
|
+
icon?: WorkspacePanelIcon;
|
|
117
|
+
order?: number;
|
|
118
|
+
visible?: (context: WorkspacePanelContext) => boolean;
|
|
119
|
+
badge?: (context: WorkspacePanelContext) => string | number | TemplateResult | undefined;
|
|
120
|
+
render: (context: WorkspacePanelContext) => TemplateResult;
|
|
121
|
+
}
|
|
122
|
+
export interface WorkspaceLabelContext extends WorkspaceContext {
|
|
123
|
+
machine: PluginMachine;
|
|
124
|
+
workspace: Workspace;
|
|
125
|
+
state?: PluginRuntimeState;
|
|
126
|
+
files: WorkspaceFiles;
|
|
127
|
+
host: WorkspaceHost;
|
|
128
|
+
}
|
|
129
|
+
export type WorkspaceLabelItem = WorkspaceLabelTextItem | WorkspaceLabelLinkItem | WorkspaceLabelRenderItem;
|
|
130
|
+
export interface WorkspaceLabelTextItem {
|
|
131
|
+
type: "text";
|
|
132
|
+
text: string;
|
|
133
|
+
title?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface WorkspaceLabelLinkItem {
|
|
136
|
+
type: "link";
|
|
137
|
+
text: string;
|
|
138
|
+
href: string;
|
|
139
|
+
title?: string;
|
|
140
|
+
target?: "_blank" | "_self";
|
|
141
|
+
}
|
|
142
|
+
export interface WorkspaceLabelRenderItem {
|
|
143
|
+
type: "render";
|
|
144
|
+
render: () => TemplateResult;
|
|
145
|
+
}
|
|
146
|
+
export interface WorkspaceLabelContribution {
|
|
147
|
+
id: LocalContributionId;
|
|
148
|
+
order?: number;
|
|
149
|
+
visible?: (context: WorkspaceLabelContext) => boolean;
|
|
150
|
+
items: (context: WorkspaceLabelContext) => WorkspaceLabelItem[];
|
|
151
|
+
}
|
|
152
|
+
export type ThemeColorScheme = "dark" | "light";
|
|
153
|
+
export type ThemeTokens = Record<string, string>;
|
|
154
|
+
export interface ThemeContribution {
|
|
155
|
+
id: LocalContributionId;
|
|
156
|
+
name: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
order?: number;
|
|
159
|
+
colorScheme: ThemeColorScheme;
|
|
160
|
+
tokens: ThemeTokens;
|
|
161
|
+
}
|
|
162
|
+
export interface ThemePairContribution {
|
|
163
|
+
id: LocalContributionId;
|
|
164
|
+
name: string;
|
|
165
|
+
description?: string;
|
|
166
|
+
order?: number;
|
|
167
|
+
light: LocalContributionId;
|
|
168
|
+
dark: LocalContributionId;
|
|
169
|
+
}
|
package/dist/server/app.js
CHANGED
|
@@ -9,29 +9,23 @@ import { ProjectService } from "./projects/projectService.js";
|
|
|
9
9
|
import { WorkspaceService } from "./workspaces/workspaceService.js";
|
|
10
10
|
import { listFileSuggestions, listPathSuggestions } from "./workspaces/fileSuggestions.js";
|
|
11
11
|
import { listDirectorySuggestions } from "./projects/directorySuggestions.js";
|
|
12
|
+
import { SessionDaemonClient } from "../sessiond/sessionDaemonClient.js";
|
|
12
13
|
import { registerSessionProxyRoutes } from "./sessiond/sessionProxyRoutes.js";
|
|
13
14
|
import { registerWorkspaceExplorerRoutes } from "./workspaceExplorerRoutes.js";
|
|
14
15
|
import { registerGitRoutes } from "./gitRoutes.js";
|
|
15
16
|
import { registerTerminalProxyRoutes } from "./terminalProxyRoutes.js";
|
|
17
|
+
import { registerWorkspaceDeletionRoutes } from "./workspaces/workspaceDeletionRoutes.js";
|
|
18
|
+
import { registerConfigRoutes } from "./configRoutes.js";
|
|
16
19
|
import { PiWebPluginService } from "./piWebPluginService.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
app.get(
|
|
25
|
-
app.
|
|
26
|
-
const asset = await piWebPlugins.readAsset(request.params.pluginId, request.params["*"]);
|
|
27
|
-
if (asset === undefined)
|
|
28
|
-
return reply.code(404).send({ error: "Plugin asset not found" });
|
|
29
|
-
return reply.type(asset.contentType).send(asset.content);
|
|
30
|
-
});
|
|
31
|
-
app.get("/api/pi-web/status", async () => getPiWebStatus());
|
|
32
|
-
app.get("/api/pi-web/version", async () => getPiWebVersionStatus());
|
|
33
|
-
app.get("/api/projects", async () => projects.list());
|
|
34
|
-
app.post("/api/projects", async (request, reply) => {
|
|
20
|
+
import { createPiWebStatusCache } from "./piWebStatusCache.js";
|
|
21
|
+
import { getPiWebRuntime, getPiWebStatus, getPiWebVersionStatus } from "./piWebStatus.js";
|
|
22
|
+
import { MachineService } from "./machines/machineService.js";
|
|
23
|
+
import { registerMachineRoutes } from "./machines/machineRoutes.js";
|
|
24
|
+
import { registerMachineProxyRoutes } from "./machines/machineProxyRoutes.js";
|
|
25
|
+
import { proxyMachinePluginAsset, registerMachinePluginProxyRoutes } from "./machines/machinePluginProxyRoutes.js";
|
|
26
|
+
function registerLocalProjectRoutes(app, projects, workspaces, prefix) {
|
|
27
|
+
app.get(`${prefix}/projects`, async () => projects.list());
|
|
28
|
+
app.post(`${prefix}/projects`, async (request, reply) => {
|
|
35
29
|
try {
|
|
36
30
|
return await projects.add(request.body);
|
|
37
31
|
}
|
|
@@ -39,7 +33,7 @@ export async function buildApp(deps = {}) {
|
|
|
39
33
|
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
|
|
40
34
|
}
|
|
41
35
|
});
|
|
42
|
-
app.delete(
|
|
36
|
+
app.delete(`${prefix}/projects/:projectId`, async (request, reply) => {
|
|
43
37
|
try {
|
|
44
38
|
await projects.close(request.params.projectId);
|
|
45
39
|
return { closed: true };
|
|
@@ -48,7 +42,7 @@ export async function buildApp(deps = {}) {
|
|
|
48
42
|
return reply.code(404).send({ error: error instanceof Error ? error.message : String(error) });
|
|
49
43
|
}
|
|
50
44
|
});
|
|
51
|
-
app.get(
|
|
45
|
+
app.get(`${prefix}/project-directories`, async (request, reply) => {
|
|
52
46
|
try {
|
|
53
47
|
return await listDirectorySuggestions(request.query.q ?? "");
|
|
54
48
|
}
|
|
@@ -56,7 +50,7 @@ export async function buildApp(deps = {}) {
|
|
|
56
50
|
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
|
|
57
51
|
}
|
|
58
52
|
});
|
|
59
|
-
app.get(
|
|
53
|
+
app.get(`${prefix}/projects/:projectId/workspaces`, async (request, reply) => {
|
|
60
54
|
try {
|
|
61
55
|
const project = await projects.requireProject(request.params.projectId);
|
|
62
56
|
return await workspaces.list(project);
|
|
@@ -65,11 +59,9 @@ export async function buildApp(deps = {}) {
|
|
|
65
59
|
return reply.code(404).send({ error: error instanceof Error ? error.message : String(error) });
|
|
66
60
|
}
|
|
67
61
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
registerTerminalProxyRoutes(app, projects, workspaces);
|
|
72
|
-
app.get("/api/files", async (request, reply) => {
|
|
62
|
+
}
|
|
63
|
+
function registerLocalFileSuggestionRoutes(app, prefix) {
|
|
64
|
+
app.get(`${prefix}/files`, async (request, reply) => {
|
|
73
65
|
if (request.query.cwd === undefined || request.query.cwd === "")
|
|
74
66
|
return reply.code(400).send({ error: "cwd query parameter is required" });
|
|
75
67
|
try {
|
|
@@ -81,6 +73,51 @@ export async function buildApp(deps = {}) {
|
|
|
81
73
|
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
|
|
82
74
|
}
|
|
83
75
|
});
|
|
76
|
+
}
|
|
77
|
+
export async function buildApp(deps = {}) {
|
|
78
|
+
const app = Fastify({ logger: deps.logger ?? true });
|
|
79
|
+
await app.register(fastifyWebsocket);
|
|
80
|
+
const projects = deps.projects ?? new ProjectService(new ProjectStore());
|
|
81
|
+
const workspaces = deps.workspaces ?? new WorkspaceService();
|
|
82
|
+
const piWebPlugins = deps.piWebPlugins ?? new PiWebPluginService();
|
|
83
|
+
const sessionDaemon = deps.sessionDaemon ?? new SessionDaemonClient();
|
|
84
|
+
const piWebStatusCache = createPiWebStatusCache(() => getPiWebStatus(sessionDaemon), {
|
|
85
|
+
onError: (error) => { app.log.warn({ err: error }, "failed to refresh PI WEB status cache"); },
|
|
86
|
+
});
|
|
87
|
+
const machines = deps.machines ?? new MachineService(undefined, {
|
|
88
|
+
localRuntime: () => getPiWebRuntime(sessionDaemon),
|
|
89
|
+
});
|
|
90
|
+
app.get("/pi-web-plugins/manifest.json", async () => piWebPlugins.manifest());
|
|
91
|
+
app.get("/pi-web-plugins/:pluginId/*", async (request, reply) => {
|
|
92
|
+
if (await proxyMachinePluginAsset(machines, request.params.pluginId, request.params["*"], request.url, reply))
|
|
93
|
+
return;
|
|
94
|
+
const asset = await piWebPlugins.readAsset(request.params.pluginId, request.params["*"]);
|
|
95
|
+
if (asset === undefined)
|
|
96
|
+
return reply.code(404).send({ error: "Plugin asset not found" });
|
|
97
|
+
return reply.type(asset.contentType).send(asset.content);
|
|
98
|
+
});
|
|
99
|
+
app.get("/api/pi-web/status", async () => piWebStatusCache.get());
|
|
100
|
+
app.get("/api/pi-web/version", async () => getPiWebVersionStatus(sessionDaemon));
|
|
101
|
+
app.get("/api/pi-web/runtime", async () => getPiWebRuntime(sessionDaemon));
|
|
102
|
+
app.get("/api/plugins", async () => piWebPlugins.plugins());
|
|
103
|
+
registerConfigRoutes(app, deps.config);
|
|
104
|
+
registerMachineRoutes(app, machines);
|
|
105
|
+
registerMachinePluginProxyRoutes(app, machines);
|
|
106
|
+
registerLocalProjectRoutes(app, projects, workspaces, "/api");
|
|
107
|
+
registerLocalProjectRoutes(app, projects, workspaces, "/api/machines/local");
|
|
108
|
+
registerSessionProxyRoutes(app, sessionDaemon);
|
|
109
|
+
registerSessionProxyRoutes(app, sessionDaemon, "/api/machines/local");
|
|
110
|
+
registerWorkspaceExplorerRoutes(app, projects, workspaces);
|
|
111
|
+
registerWorkspaceExplorerRoutes(app, projects, workspaces, "/api/machines/local");
|
|
112
|
+
registerGitRoutes(app, projects, workspaces);
|
|
113
|
+
registerGitRoutes(app, projects, workspaces, "/api/machines/local");
|
|
114
|
+
registerTerminalProxyRoutes(app, projects, workspaces, sessionDaemon);
|
|
115
|
+
registerTerminalProxyRoutes(app, projects, workspaces, sessionDaemon, "/api/machines/local");
|
|
116
|
+
registerWorkspaceDeletionRoutes(app, projects, workspaces, sessionDaemon);
|
|
117
|
+
registerWorkspaceDeletionRoutes(app, projects, workspaces, sessionDaemon, "/api/machines/local");
|
|
118
|
+
registerLocalFileSuggestionRoutes(app, "/api");
|
|
119
|
+
registerLocalFileSuggestionRoutes(app, "/api/machines/local");
|
|
120
|
+
registerMachineProxyRoutes(app, machines);
|
|
84
121
|
const packagedClientDist = join(dirname(fileURLToPath(import.meta.url)), "..", "client");
|
|
85
122
|
const clientDist = deps.clientDist ?? (existsSync(packagedClientDist) ? packagedClientDist : join(process.cwd(), "dist", "client"));
|
|
86
123
|
if (clientDist !== false && existsSync(clientDist)) {
|