@hachej/boring-ui-cli 0.1.35 → 0.1.37
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/dist/server/cli.js +19 -579
- package/dist/server/modeApps.js +626 -0
- package/dist/server/pluginDiscovery.js +25 -4
- package/dist/server/pluginFrontRuntime.js +86 -17
- package/package.json +6 -5
- package/public/assets/{DebugDrawer-CmfCaxFo.js → DebugDrawer-DnJihn7d.js} +1 -1
- package/public/assets/{_baseUniq-C4cssnmI.js → _baseUniq-Css5uykF.js} +1 -1
- package/public/assets/{arc-wdyRM9Dj.js → arc-CnfZXaWe.js} +1 -1
- package/public/assets/{architectureDiagram-Q4EWVU46-BxGI7D7J.js → architectureDiagram-Q4EWVU46-BANSd98t.js} +1 -1
- package/public/assets/{blockDiagram-DXYQGD6D-Dn29DmW4.js → blockDiagram-DXYQGD6D-Cd6iEbZF.js} +1 -1
- package/public/assets/{c4Diagram-AHTNJAMY-C1tj_DPA.js → c4Diagram-AHTNJAMY-9WJupXFB.js} +1 -1
- package/public/assets/channel-Cn1SBioe.js +1 -0
- package/public/assets/{chunk-4BX2VUAB-CHT4yrPF.js → chunk-4BX2VUAB-DEE8UItM.js} +1 -1
- package/public/assets/{chunk-4TB4RGXK-DnESVf52.js → chunk-4TB4RGXK-Bf4S3aFF.js} +1 -1
- package/public/assets/{chunk-55IACEB6-CMiNXUlN.js → chunk-55IACEB6-rumvIfCQ.js} +1 -1
- package/public/assets/{chunk-EDXVE4YY-DdgwYS0K.js → chunk-EDXVE4YY-B41LsR8o.js} +1 -1
- package/public/assets/{chunk-FMBD7UC4-BAOIovY_.js → chunk-FMBD7UC4-Cq0MvVHl.js} +1 -1
- package/public/assets/{chunk-OYMX7WX6-Cbvpbjce.js → chunk-OYMX7WX6-AEfKb-L7.js} +1 -1
- package/public/assets/{chunk-QZHKN3VN-CoKffRLQ.js → chunk-QZHKN3VN-BnF9Mgjw.js} +1 -1
- package/public/assets/{chunk-YZCP3GAM-C7-eq7rZ.js → chunk-YZCP3GAM-CVhtG4Yd.js} +1 -1
- package/public/assets/classDiagram-6PBFFD2Q-B7iAGzAt.js +1 -0
- package/public/assets/classDiagram-v2-HSJHXN6E-B7iAGzAt.js +1 -0
- package/public/assets/clone-CKRPa4Cx.js +1 -0
- package/public/assets/{cose-bilkent-S5V4N54A-C1S6rZwh.js → cose-bilkent-S5V4N54A-BAlza9SS.js} +1 -1
- package/public/assets/{dagre-KV5264BT-Ct0pKZ0k.js → dagre-KV5264BT-DXC-wg6w.js} +1 -1
- package/public/assets/{diagram-5BDNPKRD-YH70HUnu.js → diagram-5BDNPKRD-DQ_JZ2v6.js} +1 -1
- package/public/assets/{diagram-G4DWMVQ6-C4odz4kl.js → diagram-G4DWMVQ6-BV1_oXdH.js} +1 -1
- package/public/assets/{diagram-MMDJMWI5-BmJzg7kb.js → diagram-MMDJMWI5-CBT9iuoD.js} +1 -1
- package/public/assets/{diagram-TYMM5635-C3ojF6WX.js → diagram-TYMM5635-2gLQa-5c.js} +1 -1
- package/public/assets/{erDiagram-SMLLAGMA-CCv0YvhQ.js → erDiagram-SMLLAGMA-BlACgWIF.js} +1 -1
- package/public/assets/{flowDiagram-DWJPFMVM-cOU32gSH.js → flowDiagram-DWJPFMVM-C3a4Cakw.js} +1 -1
- package/public/assets/{ganttDiagram-T4ZO3ILL-DBfxNCML.js → ganttDiagram-T4ZO3ILL-BvO6eI9n.js} +1 -1
- package/public/assets/{gitGraphDiagram-UUTBAWPF-BpeXvr1_.js → gitGraphDiagram-UUTBAWPF-CNRpcuUc.js} +1 -1
- package/public/assets/{graph-nhMaYPvi.js → graph-BGcM9Vra.js} +1 -1
- package/public/assets/{highlighted-body-OFNGDK62--BzSfCvR.js → highlighted-body-OFNGDK62-DFAlA8ty.js} +1 -1
- package/public/assets/{index-BMuBL8Qv.js → index-DuQz_ooq.js} +476 -471
- package/public/assets/index-o6MvaI3V.css +1 -0
- package/public/assets/{infoDiagram-42DDH7IO-TgQUdnii.js → infoDiagram-42DDH7IO-tqWZWhv9.js} +1 -1
- package/public/assets/{ishikawaDiagram-UXIWVN3A-BTf1ZnbI.js → ishikawaDiagram-UXIWVN3A-BSrhE5rT.js} +1 -1
- package/public/assets/{journeyDiagram-VCZTEJTY-CE5ZvQD-.js → journeyDiagram-VCZTEJTY-p4zWU15s.js} +1 -1
- package/public/assets/{kanban-definition-6JOO6SKY-DdedFuqF.js → kanban-definition-6JOO6SKY-Bh1g0QjZ.js} +1 -1
- package/public/assets/{layout-Cih797KL.js → layout-gyEZgedN.js} +1 -1
- package/public/assets/{linear-C5C4CuFZ.js → linear-D_hzHorE.js} +1 -1
- package/public/assets/{min-7u-C0OaV.js → min-DuaweV_G.js} +1 -1
- package/public/assets/{mindmap-definition-QFDTVHPH-OBZRxcri.js → mindmap-definition-QFDTVHPH-Bd9cPaCZ.js} +1 -1
- package/public/assets/{pieDiagram-DEJITSTG-D8ztumiz.js → pieDiagram-DEJITSTG-BtC28nvk.js} +1 -1
- package/public/assets/{quadrantDiagram-34T5L4WZ-CNNRXkDc.js → quadrantDiagram-34T5L4WZ-D4l6_MzD.js} +1 -1
- package/public/assets/{requirementDiagram-MS252O5E-Cwbaa0Qx.js → requirementDiagram-MS252O5E-C1iUHr18.js} +1 -1
- package/public/assets/{sankeyDiagram-XADWPNL6-DBmviVXl.js → sankeyDiagram-XADWPNL6-OVDB_8ZR.js} +1 -1
- package/public/assets/{sequenceDiagram-FGHM5R23-ppAZl-uV.js → sequenceDiagram-FGHM5R23-BDavACVC.js} +1 -1
- package/public/assets/{stateDiagram-FHFEXIEX-C168rmPR.js → stateDiagram-FHFEXIEX-q8RwQFTP.js} +1 -1
- package/public/assets/stateDiagram-v2-QKLJ7IA2-BoiN-2KM.js +1 -0
- package/public/assets/{timeline-definition-GMOUNBTQ-CXM9zfSi.js → timeline-definition-GMOUNBTQ-y0_Knxi9.js} +1 -1
- package/public/assets/{vennDiagram-DHZGUBPP-GDYdqb9p.js → vennDiagram-DHZGUBPP-DbnSIyoN.js} +1 -1
- package/public/assets/{wardley-RL74JXVD-z_pLyMzD.js → wardley-RL74JXVD-CIj9kuqP.js} +1 -1
- package/public/assets/{wardleyDiagram-NUSXRM2D-ClQNpAKT.js → wardleyDiagram-NUSXRM2D--fILgFoZ.js} +1 -1
- package/public/assets/{xychartDiagram-5P7HB3ND-BThhBVRv.js → xychartDiagram-5P7HB3ND-YFiXEsiS.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/channel-zS0VgreV.js +0 -1
- package/public/assets/classDiagram-6PBFFD2Q-DF_m3Xeb.js +0 -1
- package/public/assets/classDiagram-v2-HSJHXN6E-DF_m3Xeb.js +0 -1
- package/public/assets/clone-DcXiz7Vf.js +0 -1
- package/public/assets/index-CR04sFQU.css +0 -1
- package/public/assets/stateDiagram-v2-QKLJ7IA2-D6wQK0MH.js +0 -1
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { createLocalWorkspaceRegistry } from "./localWorkspaces.js";
|
|
6
|
+
import { resolveBoringUiCliPackageRoot } from "./pluginDiscovery.js";
|
|
7
|
+
const MODE_MAP = {
|
|
8
|
+
"local": "direct",
|
|
9
|
+
// no sandbox, full network access
|
|
10
|
+
"local-sandbox": "local"
|
|
11
|
+
// bwrap isolated, no network (Linux only)
|
|
12
|
+
};
|
|
13
|
+
const require2 = createRequire(import.meta.url);
|
|
14
|
+
const PLUGIN_CLI_PACKAGE_NAME = "@hachej/boring-ui-plugin-cli";
|
|
15
|
+
const CLI_VERSION = (() => {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = require2("../../package.json");
|
|
18
|
+
return pkg.version ?? "0.0.0";
|
|
19
|
+
} catch {
|
|
20
|
+
return "0.0.0";
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
function httpError(message, statusCode) {
|
|
24
|
+
const error = new Error(message);
|
|
25
|
+
error.statusCode = statusCode;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
function firstString(value) {
|
|
29
|
+
if (typeof value === "string") return value;
|
|
30
|
+
if (!Array.isArray(value)) return void 0;
|
|
31
|
+
return value.find((item) => typeof item === "string");
|
|
32
|
+
}
|
|
33
|
+
function resolveWorkspaceIdFromRequest(request) {
|
|
34
|
+
const headers = request.headers ?? {};
|
|
35
|
+
const headerValue = headers["x-boring-workspace-id"] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === "x-boring-workspace-id")?.[1];
|
|
36
|
+
const query = request.query;
|
|
37
|
+
const raw = firstString(headerValue) ?? firstString(query?.workspaceId) ?? "";
|
|
38
|
+
const workspaceId = raw.trim();
|
|
39
|
+
if (!workspaceId) throw httpError("workspace id is required", 400);
|
|
40
|
+
if (workspaceId.includes("\0") || workspaceId.includes("/") || workspaceId.includes("\\") || workspaceId.includes("..") || isAbsolute(workspaceId)) {
|
|
41
|
+
throw httpError("invalid workspace id", 400);
|
|
42
|
+
}
|
|
43
|
+
return workspaceId;
|
|
44
|
+
}
|
|
45
|
+
function toCoreWorkspace(workspace) {
|
|
46
|
+
return {
|
|
47
|
+
id: workspace.id,
|
|
48
|
+
name: workspace.name,
|
|
49
|
+
slug: workspace.id,
|
|
50
|
+
isDefault: false,
|
|
51
|
+
createdAt: workspace.createdAt,
|
|
52
|
+
updatedAt: workspace.updatedAt,
|
|
53
|
+
unavailable: !workspace.available,
|
|
54
|
+
path: workspace.path
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function isUsableBoringUiPluginCliPackageRoot(candidate) {
|
|
58
|
+
try {
|
|
59
|
+
const pkg = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
|
|
60
|
+
return pkg.name === PLUGIN_CLI_PACKAGE_NAME && existsSync(join(candidate, "dist", "bin.js"));
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function resolveBoringUiPluginCliPackageRoot() {
|
|
66
|
+
const cliRoot = resolveBoringUiCliPackageRoot();
|
|
67
|
+
const candidate = resolve(cliRoot, "..", "plugin-cli");
|
|
68
|
+
return isUsableBoringUiPluginCliPackageRoot(candidate) ? candidate : null;
|
|
69
|
+
}
|
|
70
|
+
function createBoringUiCliRuntimePlugin() {
|
|
71
|
+
const useLocal = process.env.BORING_USE_LOCAL_PACKAGES === "1";
|
|
72
|
+
const packageRoot = useLocal ? resolveBoringUiPluginCliPackageRoot() : null;
|
|
73
|
+
return {
|
|
74
|
+
id: "boring-ui-plugin-cli-runtime",
|
|
75
|
+
provisioning: {
|
|
76
|
+
nodePackages: [{
|
|
77
|
+
id: "boring-ui-plugin-cli",
|
|
78
|
+
packageName: PLUGIN_CLI_PACKAGE_NAME,
|
|
79
|
+
...packageRoot ? { packageRoot } : { version: CLI_VERSION },
|
|
80
|
+
expectedBins: ["boring-ui-plugin"]
|
|
81
|
+
}]
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function provisionCliWorkspaceRuntime(opts) {
|
|
86
|
+
if (opts.provisionWorkspace === false) return void 0;
|
|
87
|
+
const agent = await import("@hachej/boring-agent/server");
|
|
88
|
+
const runtimeLayout = opts.runtimeLayout ?? agent.getBoringAgentRuntimePaths(opts.workspaceRoot);
|
|
89
|
+
const adapter = opts.adapter ?? opts.modeAdapter?.createProvisioningAdapter?.(runtimeLayout) ?? agent.resolveMode(opts.mode).createProvisioningAdapter?.(runtimeLayout);
|
|
90
|
+
if (!adapter) {
|
|
91
|
+
throw new Error(`runtime mode ${opts.mode} does not support workspace provisioning`);
|
|
92
|
+
}
|
|
93
|
+
const result = await agent.provisionWorkspaceRuntime({
|
|
94
|
+
plugins: [createBoringUiCliRuntimePlugin(), ...opts.plugins ?? []],
|
|
95
|
+
adapter,
|
|
96
|
+
runtimeLayout
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
...result,
|
|
100
|
+
env: {
|
|
101
|
+
...result.env,
|
|
102
|
+
BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS: opts.mode === "direct" || opts.mode === "local" ? "1" : "0"
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID = "folder";
|
|
107
|
+
const RUNTIME_PLUGIN_TRUST_LABEL = "Trusted local runtime plugins";
|
|
108
|
+
const RUNTIME_PLUGIN_TRUST_DESCRIPTION = "Loads plugin UI code from trusted local Pi extension roots through the CLI-owned runtime module host.";
|
|
109
|
+
function createRuntimePluginDiagnosticsStore() {
|
|
110
|
+
const byWorkspace = /* @__PURE__ */ new Map();
|
|
111
|
+
function upsert(workspaceId, pluginId) {
|
|
112
|
+
const workspace = byWorkspace.get(workspaceId) ?? /* @__PURE__ */ new Map();
|
|
113
|
+
byWorkspace.set(workspaceId, workspace);
|
|
114
|
+
const existing = workspace.get(pluginId) ?? {
|
|
115
|
+
workspaceId,
|
|
116
|
+
pluginId,
|
|
117
|
+
recent: []
|
|
118
|
+
};
|
|
119
|
+
workspace.set(pluginId, existing);
|
|
120
|
+
return existing;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
record(diagnostic) {
|
|
124
|
+
if (!diagnostic.workspaceId || !diagnostic.pluginId) return;
|
|
125
|
+
const entry = upsert(diagnostic.workspaceId, diagnostic.pluginId);
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
entry.lastDiagnostic = diagnostic;
|
|
128
|
+
entry.recent = [...entry.recent, diagnostic].slice(-12);
|
|
129
|
+
if (diagnostic.revision !== void 0) entry.revision = diagnostic.revision;
|
|
130
|
+
if (diagnostic.requestedPath) entry.lastRequestedPath = diagnostic.requestedPath;
|
|
131
|
+
if (diagnostic.resolvedPath) entry.lastResolvedPath = diagnostic.resolvedPath;
|
|
132
|
+
const details = diagnostic.details ?? {};
|
|
133
|
+
if (typeof details.rootDir === "string") entry.rootDir = details.rootDir;
|
|
134
|
+
if (typeof details.entryUrl === "string") entry.entryUrl = details.entryUrl;
|
|
135
|
+
if (typeof diagnostic.requestedPath === "string") entry.frontEntrySubpath = diagnostic.requestedPath;
|
|
136
|
+
if (diagnostic.stage === "track" && diagnostic.outcome === "tracked") {
|
|
137
|
+
entry.lastErrorCode = void 0;
|
|
138
|
+
entry.lastErrorMessage = void 0;
|
|
139
|
+
entry.lastErrorStage = void 0;
|
|
140
|
+
}
|
|
141
|
+
if (diagnostic.stage === "cache") entry.lastRequestAt = now;
|
|
142
|
+
if (diagnostic.stage === "transform" && diagnostic.outcome === "served") {
|
|
143
|
+
entry.lastTransformAt = now;
|
|
144
|
+
entry.lastTransformDurationMs = diagnostic.durationMs;
|
|
145
|
+
}
|
|
146
|
+
if (diagnostic.stage === "serve" && diagnostic.outcome === "served") {
|
|
147
|
+
entry.lastServeAt = now;
|
|
148
|
+
entry.lastServeDurationMs = diagnostic.durationMs;
|
|
149
|
+
entry.lastErrorCode = void 0;
|
|
150
|
+
entry.lastErrorMessage = void 0;
|
|
151
|
+
entry.lastErrorStage = void 0;
|
|
152
|
+
}
|
|
153
|
+
if (diagnostic.outcome === "rejected") {
|
|
154
|
+
entry.lastRejectedAt = now;
|
|
155
|
+
entry.lastErrorCode = diagnostic.code;
|
|
156
|
+
entry.lastErrorMessage = diagnostic.msg;
|
|
157
|
+
entry.lastErrorStage = diagnostic.stage;
|
|
158
|
+
}
|
|
159
|
+
if (diagnostic.stage === "cleanup" && diagnostic.outcome === "disposed") {
|
|
160
|
+
entry.lastDisposedAt = now;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
snapshot(workspaceId) {
|
|
164
|
+
return [...byWorkspace.get(workspaceId)?.values() ?? []].map((entry) => ({ ...entry, recent: [...entry.recent] })).sort((a, b) => a.pluginId.localeCompare(b.pluginId));
|
|
165
|
+
},
|
|
166
|
+
disposeWorkspace(workspaceId) {
|
|
167
|
+
byWorkspace.delete(workspaceId);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, events) {
|
|
172
|
+
for (const event of events) {
|
|
173
|
+
if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
|
|
174
|
+
runtimeHost.untrackPlugin(workspaceId, event.id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function buildRuntimePluginDiagnosticsResponse(args) {
|
|
179
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
180
|
+
for (const plugin of args.loaded) {
|
|
181
|
+
byPlugin.set(plugin.id, {
|
|
182
|
+
id: plugin.id,
|
|
183
|
+
...plugin.version ? { version: plugin.version } : {},
|
|
184
|
+
...plugin.rootDir ? { rootDir: plugin.rootDir } : {},
|
|
185
|
+
...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
|
|
186
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
187
|
+
...plugin.revision !== void 0 ? { serverLoadedRevision: plugin.revision } : {}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
for (const error of args.errors) {
|
|
191
|
+
const current = byPlugin.get(error.id) ?? { id: error.id };
|
|
192
|
+
byPlugin.set(error.id, {
|
|
193
|
+
...current,
|
|
194
|
+
serverError: error.message
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
for (const hostEntry of args.host) {
|
|
198
|
+
const current = byPlugin.get(hostEntry.pluginId) ?? { id: hostEntry.pluginId };
|
|
199
|
+
byPlugin.set(hostEntry.pluginId, {
|
|
200
|
+
...current,
|
|
201
|
+
...current.rootDir ? {} : hostEntry.rootDir ? { rootDir: hostEntry.rootDir } : {},
|
|
202
|
+
...current.frontPath ? {} : hostEntry.frontEntrySubpath ? { frontPath: hostEntry.frontEntrySubpath } : {},
|
|
203
|
+
...current.frontTarget ? {} : hostEntry.entryUrl ? { frontTarget: { kind: "native", entryUrl: hostEntry.entryUrl, revision: hostEntry.revision ?? 0, trust: "local-trusted-native" } } : {},
|
|
204
|
+
host: hostEntry
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
workspaceId: args.workspaceId,
|
|
209
|
+
plugins: [...byPlugin.values()].sort((a, b) => a.id.localeCompare(b.id))
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async function createFolderModeApp(opts) {
|
|
213
|
+
const workspaceRoot = resolve(opts.workspaceRoot);
|
|
214
|
+
const projectName = opts.projectName ?? (basename(workspaceRoot) || "workspace");
|
|
215
|
+
const [{ createWorkspaceAgentServer, readWorkspacePluginPackageRuntimePlugins }, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
|
|
216
|
+
import("@hachej/boring-workspace/app/server"),
|
|
217
|
+
import("./pluginFrontRuntime.js"),
|
|
218
|
+
import("./pluginDiscovery.js")
|
|
219
|
+
]);
|
|
220
|
+
const diagnosticsStore = createRuntimePluginDiagnosticsStore();
|
|
221
|
+
const runtimeHost = await createPluginFrontRuntimeHost({
|
|
222
|
+
onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
|
|
223
|
+
});
|
|
224
|
+
const pluginDirs = pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot);
|
|
225
|
+
const runtimeProvisioning = await provisionCliWorkspaceRuntime({
|
|
226
|
+
workspaceRoot,
|
|
227
|
+
mode: opts.mode,
|
|
228
|
+
provisionWorkspace: opts.provisionWorkspace,
|
|
229
|
+
plugins: readWorkspacePluginPackageRuntimePlugins(pluginDirs)
|
|
230
|
+
});
|
|
231
|
+
const app = await createWorkspaceAgentServer({
|
|
232
|
+
workspaceRoot,
|
|
233
|
+
mode: opts.mode,
|
|
234
|
+
logger: false,
|
|
235
|
+
provisionWorkspace: false,
|
|
236
|
+
runtimeProvisioning,
|
|
237
|
+
// CLI-bundled internal plugins, resolved to absolute package dirs. This
|
|
238
|
+
// drives the server-side install array (boot-time routes/agentTools);
|
|
239
|
+
// additionalBoringPluginDirs only feeds the asset-manager scan.
|
|
240
|
+
defaultPluginPackages: pluginDiscovery.resolveCliDefaultPluginPackagePaths(),
|
|
241
|
+
additionalBoringPluginDirs: pluginDirs,
|
|
242
|
+
boringPluginFrontTargetResolver: runtimeHost.createFrontTargetResolver(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
|
|
243
|
+
});
|
|
244
|
+
await runtimeHost.registerRoutes(app);
|
|
245
|
+
const folderAssetManager = app.__boringAssetManager;
|
|
246
|
+
const closeFolderRuntimeCleanup = folderAssetManager?.subscribe((event) => {
|
|
247
|
+
syncRuntimeHostFromPluginEvents(runtimeHost, FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID, [event]);
|
|
248
|
+
});
|
|
249
|
+
if (closeFolderRuntimeCleanup) {
|
|
250
|
+
app.addHook("onClose", async () => {
|
|
251
|
+
closeFolderRuntimeCleanup();
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
app.get("/api/v1/runtime-plugin-diagnostics", async () => {
|
|
255
|
+
const manager = app.__boringAssetManager;
|
|
256
|
+
return buildRuntimePluginDiagnosticsResponse({
|
|
257
|
+
workspaceId: FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID,
|
|
258
|
+
loaded: manager?.inspectLoaded() ?? [],
|
|
259
|
+
errors: manager?.getErrors() ?? [],
|
|
260
|
+
host: diagnosticsStore.snapshot(FOLDER_RUNTIME_PLUGIN_WORKSPACE_ID)
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
app.get("/api/v1/workspace/meta", async () => ({
|
|
264
|
+
workspaceId: "default",
|
|
265
|
+
workspaceRoot,
|
|
266
|
+
projectName,
|
|
267
|
+
version: CLI_VERSION,
|
|
268
|
+
runtimePluginFrontLoadingEnabled: true,
|
|
269
|
+
runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
|
|
270
|
+
runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
|
|
271
|
+
runtimePluginDiagnosticsEnabled: true
|
|
272
|
+
}));
|
|
273
|
+
return app;
|
|
274
|
+
}
|
|
275
|
+
async function createWorkspacesModeApp(opts) {
|
|
276
|
+
const [workspaceAppServer, workspaceServer, agentServer, agentShared, fastifyModule, { createPluginFrontRuntimeHost }, pluginDiscovery] = await Promise.all([
|
|
277
|
+
import("@hachej/boring-workspace/app/server"),
|
|
278
|
+
import("@hachej/boring-workspace/server"),
|
|
279
|
+
import("@hachej/boring-agent/server"),
|
|
280
|
+
import("@hachej/boring-agent/shared"),
|
|
281
|
+
import("fastify"),
|
|
282
|
+
import("./pluginFrontRuntime.js"),
|
|
283
|
+
import("./pluginDiscovery.js")
|
|
284
|
+
]);
|
|
285
|
+
const registry = createLocalWorkspaceRegistry(opts.registryPath);
|
|
286
|
+
const app = fastifyModule.default({ logger: false, bodyLimit: 16 * 1024 * 1024 });
|
|
287
|
+
const diagnosticsStore = createRuntimePluginDiagnosticsStore();
|
|
288
|
+
const runtimeHost = await createPluginFrontRuntimeHost({
|
|
289
|
+
onDiagnostic: (diagnostic) => diagnosticsStore.record(diagnostic)
|
|
290
|
+
});
|
|
291
|
+
await runtimeHost.registerRoutes(app);
|
|
292
|
+
await app.register(workspaceServer.runtimeBackendGateway, {
|
|
293
|
+
registry: {
|
|
294
|
+
dispatch: async (dispatchRequest) => {
|
|
295
|
+
if (!dispatchRequest.workspaceId) {
|
|
296
|
+
throw new workspaceServer.RuntimeBackendError(
|
|
297
|
+
agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
|
|
298
|
+
404,
|
|
299
|
+
"runtime backend dispatch requires a workspace id (x-boring-workspace-id header)"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
const workspace = await registry.get(dispatchRequest.workspaceId);
|
|
303
|
+
if (!workspace?.available) {
|
|
304
|
+
throw new workspaceServer.RuntimeBackendError(
|
|
305
|
+
agentShared.ErrorCode.enum.RUNTIME_PLUGIN_NOT_FOUND,
|
|
306
|
+
404,
|
|
307
|
+
`runtime backend dispatch: unknown workspace ${dispatchRequest.workspaceId}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
311
|
+
return runtime.backendRegistry.dispatch({ ...dispatchRequest, workspaceId: resolve(workspace.path) });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
const bridges = /* @__PURE__ */ new Map();
|
|
316
|
+
const workspaceEventClosers = /* @__PURE__ */ new Map();
|
|
317
|
+
const pluginRuntimes = /* @__PURE__ */ new Map();
|
|
318
|
+
const pluginPiSnapshots = /* @__PURE__ */ new Map();
|
|
319
|
+
const runtimeProvisioningByWorkspace = /* @__PURE__ */ new Map();
|
|
320
|
+
function getBridge(workspaceId) {
|
|
321
|
+
let bridge = bridges.get(workspaceId);
|
|
322
|
+
if (!bridge) {
|
|
323
|
+
bridge = workspaceServer.createInMemoryBridge();
|
|
324
|
+
bridges.set(workspaceId, bridge);
|
|
325
|
+
}
|
|
326
|
+
return bridge;
|
|
327
|
+
}
|
|
328
|
+
async function requireWorkspace(workspaceId) {
|
|
329
|
+
const workspace = await registry.get(workspaceId);
|
|
330
|
+
if (!workspace) throw httpError("unknown workspace", 404);
|
|
331
|
+
if (!workspace.available) throw httpError("workspace folder unavailable", 409);
|
|
332
|
+
return workspace;
|
|
333
|
+
}
|
|
334
|
+
async function workspaceFromRequest(request) {
|
|
335
|
+
return await requireWorkspace(resolveWorkspaceIdFromRequest(request));
|
|
336
|
+
}
|
|
337
|
+
function pluginRuntimeKey(workspace) {
|
|
338
|
+
return `${workspace.id}:${workspace.path}`;
|
|
339
|
+
}
|
|
340
|
+
function syncLoadedPluginPiSnapshot(workspace, manager) {
|
|
341
|
+
pluginPiSnapshots.set(pluginRuntimeKey(workspace), manager.inspectLoadedPiSnapshot());
|
|
342
|
+
}
|
|
343
|
+
function getOrCreatePluginRuntime(workspace) {
|
|
344
|
+
const key = pluginRuntimeKey(workspace);
|
|
345
|
+
let runtime = pluginRuntimes.get(key);
|
|
346
|
+
if (!runtime) {
|
|
347
|
+
const manager = pluginDiscovery.createCliPluginAssetManager(workspace.path, {
|
|
348
|
+
frontTargetResolver: runtimeHost.createFrontTargetResolver(workspace.id)
|
|
349
|
+
});
|
|
350
|
+
const backendRegistry = new workspaceServer.RuntimeBackendRegistry();
|
|
351
|
+
runtime = {
|
|
352
|
+
manager,
|
|
353
|
+
backendRegistry,
|
|
354
|
+
ensureLoaded: manager.load().then(async () => {
|
|
355
|
+
syncLoadedPluginPiSnapshot(workspace, manager);
|
|
356
|
+
await backendRegistry.reloadFromLoadedPlugins(manager.inspectLoaded());
|
|
357
|
+
})
|
|
358
|
+
};
|
|
359
|
+
pluginRuntimes.set(key, runtime);
|
|
360
|
+
}
|
|
361
|
+
return runtime;
|
|
362
|
+
}
|
|
363
|
+
async function getLoadedPluginRuntime(workspace) {
|
|
364
|
+
const runtime = getOrCreatePluginRuntime(workspace);
|
|
365
|
+
await runtime.ensureLoaded;
|
|
366
|
+
return runtime;
|
|
367
|
+
}
|
|
368
|
+
function getLoadedPluginPiSnapshot(workspace) {
|
|
369
|
+
return pluginPiSnapshots.get(pluginRuntimeKey(workspace)) ?? {
|
|
370
|
+
additionalSkillPaths: [],
|
|
371
|
+
packages: [],
|
|
372
|
+
extensionPaths: []
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function disposeWorkspaceRuntime(workspace) {
|
|
376
|
+
for (const close of workspaceEventClosers.get(workspace.id) ?? []) {
|
|
377
|
+
try {
|
|
378
|
+
close();
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
workspaceEventClosers.delete(workspace.id);
|
|
383
|
+
const runtimeKey = pluginRuntimeKey(workspace);
|
|
384
|
+
const runtime = pluginRuntimes.get(runtimeKey);
|
|
385
|
+
if (runtime) {
|
|
386
|
+
try {
|
|
387
|
+
await runtime.backendRegistry.close();
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
pluginRuntimes.delete(runtimeKey);
|
|
392
|
+
pluginPiSnapshots.delete(runtimeKey);
|
|
393
|
+
runtimeProvisioningByWorkspace.delete(workspace.id);
|
|
394
|
+
bridges.delete(workspace.id);
|
|
395
|
+
diagnosticsStore.disposeWorkspace(workspace.id);
|
|
396
|
+
await runtimeHost.disposeWorkspace(workspace.id);
|
|
397
|
+
}
|
|
398
|
+
function reloadDiagnostics(scan) {
|
|
399
|
+
return scan.errors.map((error) => ({
|
|
400
|
+
source: "workspaces-plugin-manager",
|
|
401
|
+
message: error.message,
|
|
402
|
+
pluginId: error.id
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
app.get("/api/v1/local-workspaces", async () => ({
|
|
406
|
+
workspaces: await registry.list()
|
|
407
|
+
}));
|
|
408
|
+
app.post("/api/v1/local-workspaces", async (request, reply) => {
|
|
409
|
+
const body = request.body;
|
|
410
|
+
if (typeof body?.path !== "string" || !body.path.trim()) {
|
|
411
|
+
return reply.code(400).send({ error: "workspace path is required" });
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const workspace = await registry.add(body.path, {
|
|
415
|
+
name: typeof body.name === "string" ? body.name : void 0
|
|
416
|
+
});
|
|
417
|
+
return { workspace };
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return reply.code(400).send({
|
|
420
|
+
error: error instanceof Error ? error.message : "unable to add workspace"
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
app.delete("/api/v1/local-workspaces/:id", async (request, reply) => {
|
|
425
|
+
const { id } = request.params;
|
|
426
|
+
const workspace = await registry.get(id);
|
|
427
|
+
await registry.remove(id);
|
|
428
|
+
if (workspace) await disposeWorkspaceRuntime(workspace);
|
|
429
|
+
return reply.send({ ok: true });
|
|
430
|
+
});
|
|
431
|
+
app.get("/api/v1/workspaces", async () => ({
|
|
432
|
+
workspaces: (await registry.list()).map(toCoreWorkspace)
|
|
433
|
+
}));
|
|
434
|
+
app.get("/api/v1/workspaces/:id", async (request, reply) => {
|
|
435
|
+
const { id } = request.params;
|
|
436
|
+
const workspace = await registry.get(id);
|
|
437
|
+
if (!workspace) return reply.code(404).send({ error: "workspace not found" });
|
|
438
|
+
return { workspace: toCoreWorkspace(workspace), role: "owner" };
|
|
439
|
+
});
|
|
440
|
+
await app.register(agentServer.registerAgentRoutes, {
|
|
441
|
+
mode: opts.mode,
|
|
442
|
+
systemPromptAppend: workspaceAppServer.buildWorkspaceContextPrompt(),
|
|
443
|
+
getSystemPromptDynamic: async ({ workspaceId }) => {
|
|
444
|
+
const workspace = await requireWorkspace(workspaceId);
|
|
445
|
+
await getLoadedPluginRuntime(workspace);
|
|
446
|
+
return getLoadedPluginPiSnapshot(workspace).systemPromptAppend;
|
|
447
|
+
},
|
|
448
|
+
getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
|
|
449
|
+
getWorkspaceRoot: async (workspaceId) => (await requireWorkspace(workspaceId)).path,
|
|
450
|
+
getSessionNamespace: async ({ workspaceId }) => `local-workspace-${workspaceId}`,
|
|
451
|
+
provisionRuntime: async ({ workspaceId, workspaceRoot, runtimeMode, runtimeLayout, provisioningAdapter }) => {
|
|
452
|
+
if (runtimeProvisioningByWorkspace.has(workspaceId)) {
|
|
453
|
+
return runtimeProvisioningByWorkspace.get(workspaceId);
|
|
454
|
+
}
|
|
455
|
+
const provisioned = await provisionCliWorkspaceRuntime({
|
|
456
|
+
workspaceRoot,
|
|
457
|
+
mode: runtimeMode,
|
|
458
|
+
provisionWorkspace: opts.provisionWorkspace,
|
|
459
|
+
adapter: provisioningAdapter,
|
|
460
|
+
runtimeLayout,
|
|
461
|
+
plugins: workspaceAppServer.readWorkspacePluginPackageRuntimePlugins(pluginDiscovery.resolveCliBoringPluginDirs(workspaceRoot))
|
|
462
|
+
});
|
|
463
|
+
runtimeProvisioningByWorkspace.set(workspaceId, provisioned);
|
|
464
|
+
return provisioned;
|
|
465
|
+
},
|
|
466
|
+
beforeReload: async ({ workspaceId }) => {
|
|
467
|
+
const workspace = await requireWorkspace(workspaceId);
|
|
468
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
469
|
+
runtime.manager.setPluginDirs(pluginDiscovery.resolveCliBoringPluginDirs(workspace.path));
|
|
470
|
+
const scan = await runtime.manager.load();
|
|
471
|
+
syncLoadedPluginPiSnapshot(workspace, runtime.manager);
|
|
472
|
+
syncRuntimeHostFromPluginEvents(runtimeHost, workspaceId, scan.events);
|
|
473
|
+
const backendReload = await runtime.backendRegistry.reloadFromLoadedPlugins(runtime.manager.inspectLoaded());
|
|
474
|
+
return {
|
|
475
|
+
restart_warnings: workspaceServer.collectRestartWarnings(scan.events),
|
|
476
|
+
diagnostics: [
|
|
477
|
+
...reloadDiagnostics(scan),
|
|
478
|
+
...backendReload.diagnostics.map((diagnostic) => ({
|
|
479
|
+
source: diagnostic.source,
|
|
480
|
+
message: diagnostic.message,
|
|
481
|
+
...diagnostic.pluginId ? { pluginId: diagnostic.pluginId } : {}
|
|
482
|
+
}))
|
|
483
|
+
]
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
getPi: async ({ workspaceId, workspaceRoot }) => {
|
|
487
|
+
const workspace = await requireWorkspace(workspaceId);
|
|
488
|
+
await getLoadedPluginRuntime(workspace);
|
|
489
|
+
return {
|
|
490
|
+
additionalSkillPaths: [join(workspaceRoot, ".agents", "skills")],
|
|
491
|
+
packages: [],
|
|
492
|
+
extensionPaths: [],
|
|
493
|
+
getHotReloadableResources: () => getLoadedPluginPiSnapshot(workspace)
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
getExtraTools: async ({ workspaceId, workspaceRoot, workspaceFsCapability }) => [
|
|
497
|
+
...workspaceServer.createWorkspaceUiTools(getBridge(workspaceId), {
|
|
498
|
+
workspaceRoot: workspaceFsCapability === "strong" ? workspaceRoot : void 0
|
|
499
|
+
})
|
|
500
|
+
]
|
|
501
|
+
});
|
|
502
|
+
await app.register(workspaceServer.uiRoutes, {
|
|
503
|
+
getWorkspaceId: async (request) => (await workspaceFromRequest(request)).id,
|
|
504
|
+
getBridge: async (request) => getBridge((await workspaceFromRequest(request)).id)
|
|
505
|
+
});
|
|
506
|
+
app.get("/api/v1/runtime-plugin-diagnostics", async (request) => {
|
|
507
|
+
const workspace = await workspaceFromRequest(request);
|
|
508
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
509
|
+
return buildRuntimePluginDiagnosticsResponse({
|
|
510
|
+
workspaceId: workspace.id,
|
|
511
|
+
loaded: runtime.manager.inspectLoaded(),
|
|
512
|
+
errors: runtime.manager.getErrors(),
|
|
513
|
+
host: diagnosticsStore.snapshot(workspace.id)
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
app.get("/api/v1/agent-plugins", async (request) => {
|
|
517
|
+
const workspace = await workspaceFromRequest(request);
|
|
518
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
519
|
+
return runtime.manager.list();
|
|
520
|
+
});
|
|
521
|
+
app.get("/api/v1/agent-plugins/:id/error", async (request, reply) => {
|
|
522
|
+
const workspace = await workspaceFromRequest(request);
|
|
523
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
524
|
+
const { id } = request.params;
|
|
525
|
+
const error = runtime.manager.getError(id);
|
|
526
|
+
if (error == null) return reply.code(404).send({ error: "not_found" });
|
|
527
|
+
return reply.type("text/plain").send(error);
|
|
528
|
+
});
|
|
529
|
+
app.get("/api/v1/agent-plugins/events", async (request, reply) => {
|
|
530
|
+
const workspace = await workspaceFromRequest(request);
|
|
531
|
+
const runtime = await getLoadedPluginRuntime(workspace);
|
|
532
|
+
const manager = runtime.manager;
|
|
533
|
+
reply.hijack();
|
|
534
|
+
const res = reply.raw;
|
|
535
|
+
res.statusCode = 200;
|
|
536
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
537
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
538
|
+
res.setHeader("Connection", "keep-alive");
|
|
539
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
540
|
+
res.flushHeaders?.();
|
|
541
|
+
const write = (eventName, payload) => {
|
|
542
|
+
try {
|
|
543
|
+
res.write(`event: ${eventName}
|
|
544
|
+
`);
|
|
545
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
546
|
+
|
|
547
|
+
`);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
const liveQueue = [];
|
|
552
|
+
let replaying = true;
|
|
553
|
+
const unsubscribe = manager.subscribe((event) => {
|
|
554
|
+
if (event.type === "boring.plugin.unload" || event.type === "boring.plugin.load" && !event.frontTarget) {
|
|
555
|
+
runtimeHost.untrackPlugin(workspace.id, event.id);
|
|
556
|
+
}
|
|
557
|
+
const payload = {
|
|
558
|
+
...event,
|
|
559
|
+
workspaceId: workspace.id,
|
|
560
|
+
replay: false
|
|
561
|
+
};
|
|
562
|
+
if (replaying) {
|
|
563
|
+
liveQueue.push({ eventName: event.type, payload });
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
write(event.type, payload);
|
|
567
|
+
});
|
|
568
|
+
for (const plugin of manager.listExternal()) {
|
|
569
|
+
write("boring.plugin.load", {
|
|
570
|
+
type: "boring.plugin.load",
|
|
571
|
+
id: plugin.id,
|
|
572
|
+
boring: plugin.boring,
|
|
573
|
+
version: plugin.version,
|
|
574
|
+
revision: plugin.revision,
|
|
575
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
576
|
+
workspaceId: workspace.id,
|
|
577
|
+
replay: true
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
write("boring.plugin.replay-complete", {
|
|
581
|
+
type: "boring.plugin.replay-complete",
|
|
582
|
+
workspaceId: workspace.id,
|
|
583
|
+
replay: true
|
|
584
|
+
});
|
|
585
|
+
replaying = false;
|
|
586
|
+
for (const event of liveQueue) write(event.eventName, event.payload);
|
|
587
|
+
const heartbeat = setInterval(() => {
|
|
588
|
+
try {
|
|
589
|
+
res.write(": heartbeat\n\n");
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
}, 25e3);
|
|
593
|
+
const closeStream = () => {
|
|
594
|
+
clearInterval(heartbeat);
|
|
595
|
+
unsubscribe();
|
|
596
|
+
workspaceEventClosers.get(workspace.id)?.delete(closeStream);
|
|
597
|
+
try {
|
|
598
|
+
res.end();
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
const closers = workspaceEventClosers.get(workspace.id) ?? /* @__PURE__ */ new Set();
|
|
603
|
+
closers.add(closeStream);
|
|
604
|
+
workspaceEventClosers.set(workspace.id, closers);
|
|
605
|
+
request.raw.on("close", closeStream);
|
|
606
|
+
});
|
|
607
|
+
app.get("/api/v1/workspace/meta", async () => ({
|
|
608
|
+
projectName: "Boring UI",
|
|
609
|
+
workspacesMode: true,
|
|
610
|
+
version: CLI_VERSION,
|
|
611
|
+
runtimePluginFrontLoadingEnabled: true,
|
|
612
|
+
runtimePluginTrustLabel: RUNTIME_PLUGIN_TRUST_LABEL,
|
|
613
|
+
runtimePluginTrustDescription: RUNTIME_PLUGIN_TRUST_DESCRIPTION,
|
|
614
|
+
runtimePluginDiagnosticsEnabled: true
|
|
615
|
+
}));
|
|
616
|
+
return app;
|
|
617
|
+
}
|
|
618
|
+
export {
|
|
619
|
+
CLI_VERSION,
|
|
620
|
+
MODE_MAP,
|
|
621
|
+
createBoringUiCliRuntimePlugin,
|
|
622
|
+
createFolderModeApp,
|
|
623
|
+
createWorkspacesModeApp,
|
|
624
|
+
provisionCliWorkspaceRuntime,
|
|
625
|
+
resolveBoringUiPluginCliPackageRoot
|
|
626
|
+
};
|
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import {
|
|
5
6
|
BoringPluginAssetManager
|
|
6
7
|
} from "@hachej/boring-workspace/server";
|
|
7
8
|
import {
|
|
8
|
-
readWorkspacePluginPackagePiSnapshot
|
|
9
|
+
readWorkspacePluginPackagePiSnapshot,
|
|
10
|
+
resolveDefaultWorkspacePluginPackagePaths
|
|
9
11
|
} from "@hachej/boring-workspace/app/server";
|
|
10
12
|
import {
|
|
11
13
|
readPluginSourceRecords,
|
|
12
14
|
resolvePluginSourceScopePaths
|
|
13
15
|
} from "@hachej/boring-ui-plugin-cli";
|
|
16
|
+
function resolveBoringUiCliPackageRoot() {
|
|
17
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
return resolve(here, "..", "..");
|
|
19
|
+
}
|
|
14
20
|
function getGlobalPiExtensionsRoot(options = {}) {
|
|
15
21
|
return resolve(options.globalRoot ?? join(homedir(), ".pi", "agent", "extensions"));
|
|
16
22
|
}
|
|
17
23
|
function getGlobalPiAgentRoot(options = {}) {
|
|
18
24
|
return resolve(options.globalAgentRoot ?? dirname(getGlobalPiExtensionsRoot(options)));
|
|
19
25
|
}
|
|
26
|
+
const CLI_DEFAULT_PLUGIN_PACKAGES = ["@hachej/boring-ask-user"];
|
|
27
|
+
function resolveCliDefaultPluginPackagePaths() {
|
|
28
|
+
try {
|
|
29
|
+
return resolveDefaultWorkspacePluginPackagePaths({
|
|
30
|
+
anchorDir: resolveBoringUiCliPackageRoot(),
|
|
31
|
+
defaultPluginPackages: CLI_DEFAULT_PLUGIN_PACKAGES
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error("[boring-ui] failed to resolve default plugin packages:", error instanceof Error ? error.message : String(error));
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
20
38
|
function resolveCliBoringPluginDirs(workspaceRoot, options = {}) {
|
|
21
39
|
const resolvedWorkspaceRoot = resolve(workspaceRoot);
|
|
22
40
|
const globalAgentRoot = getGlobalPiAgentRoot(options);
|
|
@@ -26,7 +44,9 @@ function resolveCliBoringPluginDirs(workspaceRoot, options = {}) {
|
|
|
26
44
|
...readPluginSourceRecords(globalScope),
|
|
27
45
|
...readPluginSourceRecords(localScope)
|
|
28
46
|
];
|
|
47
|
+
const includeDefaultPackages = options.includeDefaultPackages ?? true;
|
|
29
48
|
const roots = [
|
|
49
|
+
...includeDefaultPackages ? resolveCliDefaultPluginPackagePaths().map((rootDir) => ({ rootDir, kind: "internal" })) : [],
|
|
30
50
|
{ rootDir: getGlobalPiExtensionsRoot(options), kind: "external" },
|
|
31
51
|
{ rootDir: globalScope.npmDir, kind: "external" },
|
|
32
52
|
{ rootDir: globalScope.gitDir, kind: "external" },
|
|
@@ -54,13 +74,14 @@ function createCliPluginAssetManager(workspaceRoot, options = {}) {
|
|
|
54
74
|
return new BoringPluginAssetManager({
|
|
55
75
|
pluginDirs: resolveCliBoringPluginDirs(workspaceRoot, options),
|
|
56
76
|
errorRoot: resolve(workspaceRoot, ".boring-agent", "plugin-errors"),
|
|
57
|
-
frontTargetResolver: options.frontTargetResolver
|
|
58
|
-
includeLegacyFrontUrl: options.includeLegacyFrontUrl
|
|
77
|
+
frontTargetResolver: options.frontTargetResolver
|
|
59
78
|
});
|
|
60
79
|
}
|
|
61
80
|
export {
|
|
62
81
|
createCliPluginAssetManager,
|
|
63
82
|
getGlobalPiExtensionsRoot,
|
|
64
83
|
readCliPluginPiSnapshot,
|
|
65
|
-
|
|
84
|
+
resolveBoringUiCliPackageRoot,
|
|
85
|
+
resolveCliBoringPluginDirs,
|
|
86
|
+
resolveCliDefaultPluginPackagePaths
|
|
66
87
|
};
|